Synchronizing Threads

In this section, we'll take a look at the kind of problems conflicting threads can create. Say you have an integer variable named counter , and two threads are both trying to increment that variable. Each thread also has some other work to do (which we'll simulate by sprinkling a few Sleep statements in the code), so between the time thread1 reads counter and stores its value in a local variable, increments its value, and then assigns the new value back to counter , thread2 might have already done the same thing. This means that thread1 will wipe out thread2 's work (conflicting access to a shared resource like this is called a race condition ). Because both threads are sharing the same object, counter , they conflict. You can see the code that will cause conflicts of this type in ch15_03.cs, Listing 15.3.

Listing 15.3 Two Unsynchronized Threads (ch15_03.cs)
 using System.Threading; class ch15_03 {   int counter = 0;   static void Main()   {     ch15_03 app = new ch15_03();     app.StartThreads();   }   public void StartThreads()   {     Thread thread1 = new Thread(new ThreadStart(CounterMethod1));     thread1.Name = "Thread 1";     thread1.Start();     Thread thread2 = new Thread(new ThreadStart(CounterMethod2));     thread2.Name = "Thread 2";     thread2.Start();     thread1.Join();     thread2.Join();     System.Console.WriteLine("All threads finished.");   }  public void CounterMethod1()   {   while (counter < 20)   {   int localCounterValue = counter;   if(localCounterValue < 20){   Thread.Sleep(50);   localCounterValue++;   Thread.Sleep(10);   counter = localCounterValue;   System.Console.WriteLine("Counter = {0} in {1}",   counter, Thread.CurrentThread.Name);   }   }   System.Console.WriteLine("{0} done. ", Thread.CurrentThread.Name);   }   public void CounterMethod2()   {   while (counter < 20)   {   int localCounterValue = counter;   if(localCounterValue < 20){   localCounterValue++;   Thread.Sleep(10);   counter = localCounterValue;   Thread.Sleep(5);   System.Console.WriteLine("Counter = {0} in {1}",   counter, Thread.CurrentThread.Name);   }   }   System.Console.WriteLine("{0} done. ", Thread.CurrentThread.Name);   }  } 

You can see the two threads interfering with each other when you run ch15_03. Note, for example, that after thread2 increments counter a few times, thread1 sets it back to 1, and thread2 starts all over:

 
 C:\>ch15_03 Counter = 1 in Thread 2 Counter = 2 in Thread 2 Counter = 3 in Thread 2 Counter = 4 in Thread 2 Counter = 5 in Thread 2 Counter = 1 in Thread 1 Counter = 1 in Thread 2 Counter = 2 in Thread 2 Counter = 3 in Thread 2 Counter = 4 in Thread 2 Counter = 5 in Thread 2 Counter = 6 in Thread 2 Counter = 2 in Thread 1 Counter = 2 in Thread 2 Counter = 3 in Thread 2 Counter = 4 in Thread 2 Counter = 5 in Thread 2 Counter = 6 in Thread 2     .     .     . Thread 2 done. Counter = 16 in Thread 1 Counter = 17 in Thread 1 Counter = 18 in Thread 1 Counter = 19 in Thread 1 Counter = 20 in Thread 1 Thread 1 done. All threads finished. 

Clearly, our two threads are interfering with each other. So how do we fix the problem? Our first fix is to use a lock .

Using a Lock to Synchronize Threads

A lock marks a critical section in your code, which is a code that you don't want other threads to interrupt. To use a lock, you use the .NET lock statement by passing this statement the object you want locked (which will be the current object, this , in this example). The code you put into the lock statement is run only by the current thread. Other threads will wait until you're done, removing the conflict in ch15_03.cs. You can see the new version of the code, using the lock statement, in ch15_04.cs, Listing 15.4.

Listing 15.4 Using a Lock (ch15_04.cs)
 using System.Threading; class ch15_04 {   int counter = 0;   static void Main()   {     ch15_04 app = new ch15_04();     app.StartThreads();   }   public void StartThreads()   {     Thread thread1 = new Thread(new ThreadStart(CounterMethod1));     thread1.Name = "Thread 1";     thread1.Start();     Thread thread2 = new Thread(new ThreadStart(CounterMethod2));     thread2.Name = "Thread 2";     thread2.Start();     thread1.Join();     thread2.Join();     System.Console.WriteLine("All threads finished.");   }   public void CounterMethod1()   {     while (counter < 20)     {  lock(this)   {   int localCounterValue = counter;   if(localCounterValue < 20){   Thread.Sleep(50);   localCounterValue++;   Thread.Sleep(10);   counter = localCounterValue;  System.Console.WriteLine("Counter = {0}  in {1}",  counter, Thread.Current-  Thread.Name);   }   }  }     System.Console.WriteLine("{0} done. ", Thread.CurrentThread.Name);   }   public void CounterMethod2()   {     while (counter < 20)     {  lock(this)   {   int localCounterValue = counter;   if(localCounterValue < 20){   localCounterValue++;   Thread.Sleep(10);   counter = localCounterValue;   Thread.Sleep(5);   System.Console.WriteLine("Counter = {0} in {1}",   counter, Thread.CurrentThread.Name);   }   }  }     System.Console.WriteLine("{0} done. ", Thread.CurrentThread.Name);   } } 

USING INTERLOCKED.INCREMENT AND INTERLOCKED.DECREMENT

Besides the other techniques that we'll take a look at here that you can use to synchronize threads, it's also worth mentioning that using threads to increment or decrement a counter is such a common operation that C# includes the Interlocked.Increment and Interlocked.Decrement methods to do just that. You pass these methods a reference to an integer or long variable, and these methods will increment or decrement that variable while being careful to avoid conflicts with other threads. While discussing thread conflicts, note that events provide a good way of communicating between threads, and through that kind of communication, you can eliminate conflicts.


When you run this new version of the code, the two threads no longer conflict, as you can see:

 
 C:\>ch15_04 Counter = 1 in Thread 1 Counter = 2 in Thread 2 Counter = 3 in Thread 1 Counter = 4 in Thread 2 Counter = 5 in Thread 1 Counter = 6 in Thread 2 Counter = 7 in Thread 1 Counter = 8 in Thread 2 Counter = 9 in Thread 1 Counter = 10 in Thread 2 Counter = 11 in Thread 1 Counter = 12 in Thread 2 Counter = 13 in Thread 1 Counter = 14 in Thread 2 Counter = 15 in Thread 1 Counter = 16 in Thread 2 Counter = 17 in Thread 1 Counter = 18 in Thread 2 Counter = 19 in Thread 1 Counter = 20 in Thread 2 Thread 2 done. Thread 1 done. All threads finished. 

Using a Monitor to Synchronize Threads

Another way to synchronize threads is to use a monitor . Using a monitor, you can specify which parts of your code need to be synchronized with other threads. To start synchronization with other threads, you call Monitor.Enter , and pass this method an object to synchronize on (we'll use this here). When you leave the critical section, you can call Monitor.Exit . You can see this in a new version of the code, ch15_05.cs, in Listing 15.5, which uses both Monitor.Enter and Monitor.Exit to synchronize the two threads.

Listing 15.5 Using a Monitor (ch15_05.cs)
 using System.Threading; class ch15_05 {   int counter = 0;   static void Main()   {     ch15_03 app = new ch15_05();     app.StartThreads();   }   public void StartThreads()   {     Thread thread1 = new Thread(new ThreadStart(CounterMethod1));     thread1.Name = "Thread 1";     thread1.Start();     Thread thread2 = new Thread(new ThreadStart(CounterMethod2));     thread2.Name = "Thread 2";     thread2.Start();     thread1.Join();     thread2.Join();     System.Console.WriteLine("All threads finished.");   }   public void CounterMethod1()   {     while (counter < 20)     {  Monitor.Enter(this);  int localCounterValue = counter;       if(localCounterValue < 20){         Thread.Sleep(50);         localCounterValue++;         Thread.Sleep(10);         counter = localCounterValue;         System.Console.WriteLine("Counter = {0} in {1}",           counter, Thread.CurrentThread.Name);       }  Monitor.Exit(this);  }     System.Console.WriteLine("{0} done. ", Thread.CurrentThread.Name);   }   public void CounterMethod2()   {     while (counter < 20)     {  Monitor.Enter(this);  int localCounterValue = counter;       if(localCounterValue < 20){         localCounterValue++;         Thread.Sleep(10);         counter = localCounterValue;         Thread.Sleep(5);         System.Console.WriteLine("Counter = {0} in {1}",           counter, Thread.CurrentThread.Name);       }  Monitor.Exit(this);  }     System.Console.WriteLine("{0} done. ", Thread.CurrentThread.Name);   } } 

USING THE MUTEX CLASS

You can also use a mutex object , which is much like a monitor, to protect a shared resource from access by multiple threads at the same time. Only one thread at a time can own a mutex object. The state of a mutex object is signaled when it is not owned by any thread, or nonsignaled when it is owned. To create a mutex object in C#, you use the Mutex class.


When you run ch15_05.cs, you get the same results as when using the lock statement in ch15_04.cs. The threads are synchronized successfully once again.

It's also worth noting that the Monitor class has two additional methods Wait and Pulse . The Wait method tells the CLR that you're willing to wait until continuing processing so that other threads can get some work done. When the active thread calls the Pulse method, threads that are waiting are called.



Microsoft Visual C#. NET 2003 Kick Start
Microsoft Visual C#.NET 2003 Kick Start
ISBN: 0672325470
EAN: 2147483647
Year: 2002
Pages: 181

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net