Mar 11 2010

Locking and multi-threading

Category: ThreadingMike Lovell @ 9:58 pm

I noted over the last few days a lot of questions about ‘lock’ came up on the Microsoft Newsgroups.  I’ve come up with a nice demonstration of the flow of a multi-threaded application with and without locking.

Often in a multi-threaded application, you will have shared resources that all the threads need to use.  Now if those resources do not allow simultaneous access (a good example of this is writing to a text file) you’re going to end up throwing an exception if you try and use the same shared resource from two different threads at once, and you don’t use locking.

Lets move onto the example (the project can be downloaded at the bottom of this article)…

First lets make a class which can simulate a shared object and doesn’t like being called simultaneously.  It’s going to print ‘!’ to console if a ‘collision’ has occurred (where in a real world application, an Exception would occur) when the function ‘Write’ is used - If the function isn’t called simultaneously, it will just print ‘.’

   1:  class KindOfASync
   2:  {
   3:      bool writing = false;
   4:   
   5:      public void Write()
   6:      {
   7:          if (writing)
   8:          {
   9:              Console.Write("!");
  10:              return;
  11:          }
  12:   
  13:          writing = true;
  14:   
  15:          Console.Write(".");
  16:   
  17:          writing = false;
  18:      }
  19:  }
  20:   

Okay, pretty simple class.  Lets up the complication 100x! :-)   We’ll now create a few constants to define how many threads we want to use at once and the amount of iterations (the amount of times we’ll make the ‘Write’ call to our class above).  We’ll then make two ‘Thread’ and ‘Action’ arrays, ‘___A’ will demonstrate not using locks, and ‘___B’ will demonstrate using locks.  The ‘Action’ class is a great way for us to queue up all the work we want our threads to do.

  21:   
  22:  class Program
  23:  {
  24:      const    int    ThreadsToUse    = 10;
  25:      const    int    Iterations        = 300;
  26:   
  27:   
  28:      static void Main(string[] args)
  29:      {
  30:          var myClass        = new KindOfASync();
  31:   
  32:          var threadPoolA    = new Thread[ThreadsToUse];
  33:          var threadPoolB    = new Thread[ThreadsToUse];
  34:  
  35:          var workA        = new Action[ThreadsToUse];
  36:          var workB        = new Action[ThreadsToUse];

Now lets queue up all the work we want our threads to handle.  We’ll evenly divide the iterations between our threads by using the ‘modulus %’.  We’re going to use the int ’completed’ to detect when we’ve completed our iterations

  37:   
  38:          var completed    = 0;
  39:   
  40:          for (int i=0; i < Iterations; i++)
  41:          {
  42:              var threadInUse = i % ThreadsToUse;
  43:   
  44:              workA[threadInUse] += delegate()
  45:                  {
  46:                      myClass.Write();
  47:   
  48:                      completed++;
  49:                  };
  50:   
  51:              workB[threadInUse] += delegate()
  52:                  {
  53:                      lock (myClass)
  54:                          {
  55:                              myClass.Write();
  56:                          }
  57:   
  58:                      completed++;
  59:                  };
  60:          }
  61:   

Now lets tell ‘threadPoolA’ to work through ‘workA’ (no locking) and wait for it to complete.

  62:          Console.WriteLine("Running 'threadPoolA' (WITHOUT locking)");
  63:   
  64:          for (int i=0; i<ThreadsToUse; i++)
  65:          {
  66:              threadPoolA[i] = new Thread(new ThreadStart(workA[i]));
  67:              threadPoolA[i].Start();
  68:          }
  69:   
  70:          while (completed < Iterations)
  71:          {
  72:              Thread.Sleep(10);
  73:          }
  74:   

Now lets tell ‘threadPoolB’ to work through ‘workB’ (locking) and wait for it to complete

  75:          Console.WriteLine("n");
  76:  
  77:   
  78:          completed = 0;
  79:   
  80:          Console.WriteLine("Running 'threadPoolB' (WITH locking)");
  81:   
  82:          for (int i=0; i<ThreadsToUse; i++)
  83:          {
  84:              threadPoolB[i] = new Thread(new ThreadStart(workB[i]));
  85:              threadPoolB[i].Start();
  86:          }
  87:   
  88:          while (completed < Iterations)
  89:          {
  90:              Thread.Sleep(10);
  91:          }
  92:   
  93:   
  94:          Console.ReadLine();
  95:      }
  96:  }

And the results

Running 'threadPoolA' (WITHOUT locking)
...!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!...............................................
................................................................................
..................................................!.............................
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!..............................
Running 'threadPoolB' (WITH locking)
................................................................................
................................................................................
................................................................................
............................................................

The amount of ‘!’ you get will vary depending on the amount of iterations, the amount of threads, and your processor and the other work your computer is doing when it runs.  ‘lock’ allows you to obtain a mutual-exclusive lock on an object whilst inside the statement block, releasing it when it’s complete.  Not using it correctly can cause many headaches.

Some other blogs covering the subject:

Download Visual Studio 2010 Project (6.48k)

Tags: , , , , , , ,