Sunday, February 14, 2010

Sharing data between threads

The most problematic part of threading is the sinchronization of data bethween the threads.

This is a continuation of a previous post.  Now we will talk about data sinchronization between threads.

The problem

The concurrent nature of the thread introduces new problems to the programmer, one of wich most interesting of them is the following:  when one machine reads or writes the value of one variable, normally this happens secuentially.  But if we're using threads this may be not true.  Take by example the following snippet:
   1:  //A class that will store a count
   2:  class Counter()
   3:  {
   4:      public static int count;
   5:      
   6:  }
   7:   
   8:  //A method that increases the count
   9:  static void Increase()
  10:      {
  11:            for(int x= 0; x < 10000; x++)
  12:            {
  13:                  Counter.Count++;
  14:            }
  15:      }
  16:   
  17:  //Now invoke the increase method from various threads
  18:   
  19:  ThreadStart theStarter = new ThreadStatrt(Increase);
  20:  Thread[] threads = new Thread[10];
  21:   
  22:  for(int x=0; x < 10; x++)
  23:  {
  24:        threads[x] = new Thread(theStarter);
  25:        threads[x].Start();      
  26:  }
  27:   
  28:  //Wait the threads to complete
  29:   
  30:  for(int x=0; x < 10; x++)
  31:  {
  32:        threads[x].Join();        
  33:  }
May be you expect that each thread increases the counter by 10000, wich happens in a not threading application.  But here, as the threads access the count variable often simultaneously, some counts are lost.
  Because this problem the .Net framework provide us with classes that allows to control the access to that variables.

Interlocked class

The interlocked class allows us to realize mathematical operations with thread safety.  All we need for solving the previous problem is to replace
   1:  for(int x= 0; x < 10000; x++)
   2:     {
   3:                  Counter.Count++;
   4:     }
   5:      

for

   1:  for(int x= 0; x < 10000; x++)
   2:     {
   3:                  Interlocked.Increment(Counter);
   4:     }  

Done this, the threads wait until the variable had been unlocked by the previous thread.  They don't access simultaneously it anymore.

Sinchronization locks

The interlocked class solves the case of a single mathematical operation, but may be you want thread safety along a code stretch, and not may be not only for mathematical operations.  This is when the lock class shows their utility:
   1:  //We added a member to this class
   2:  class Counter
   3:  {
   4:     static int count;
   5:     static int evenCount;
   6:   
   7:   
   8:  static void Update()
   9:  {
  10:     for(int x=0; x < 10000; x++)
  11:     {
  12:           /*This produces threading unwanted behavior, because Interlocked
  13:           only blocks a single operation as atomic.
  14:           Interlocked.Increase(ref Count);
  15:           Interlocked.Increase(ref evenCount);*/
  16:   
  17:           /*Instead, a sinchronization lock can enclose a code region as an atomic operation:*/
  18:   
  19:           lock(this)
  20:           {
  21:                Counter.count++;
  22:                if(Counter.count % 2 == 0)
  23:                {
  24:                     Counter.evenCount++;
  25:                }             
  26:           }         
  27:     }   

The Monitor class

Behind the scene, the Interlocked and the sinchronization lock uses the Monitor class, a lightweight object that allow us to have more control of the manner in wich the lock happens.  This class have the following static methods:
  • EnterGets a lock on the an object.   If this isn't posible raises an WaitHandleCannotBeOpenedException
  • Exit  Releases the blocked object.
  • TryEnter: Tryes to get locked an object.  It have an optional parameter for specify the time trying to get the lock.
  • Wait:  Releases the lock, blocking the current thread until it can reaquiere the object.
One of the mos dangerous situations that can be happen is the deadlock.  It occurs when a thread is blocking a resource an waiting to block another, but other thread is blocking that resource (two threads blocking each the other).  This can avoided with the TryEnter method.

The ReadWriterLock class

This class allows multiple reading threads on an object, but only an writing thtread at a time.  This class have properties and methods for indicate when a lock is acquiared, for upgrade an reader lock to writer lock and vice versa.

Windows kernel objects

Apart from the precedent blocking methods, the .Net framework provide us with heavyweigth native kernel objects of Windows, wich have the capabilty of control blocks between application domains and process boundaries:
  • Mutex:  Works simillary to the Monitor class, but it allows blocks across application domains.
  • Semaphore:  Acts as a pool of threads.  Serves to control the access to a limitated resource.  Allows you to specify how many threads can access the resource, and to relase a determined numer of them.
  • Event:  Allows to notify different threads across application domains and process boundaries that an event has happen.

No comments:

Post a Comment

Bookmark and Share