22.7 Synchronizing Variable Access Using the Monitor

 <  Day Day Up  >  

You want to prevent multiple threads from accessing the same variable simultaneously .


Technique

If multiple threads might have access to some object, then the easiest way to synchronize access is by locking the object with the C# lock statement.

Suppose some variable x contains a reference to the object in question. Then, you simply enclose all code statements that access x in a lock block:

 
 lock (x) {         // code that accesses x here. } 

The code enclosed in the lock statement is known as a protected block because it is protected against simultaneous variable access by multiple threads.

Bear in mind that x must be a reference type; it is not possible to use this technique on value types. If you need to synchronize access to a value type, the easiest way is to do this:

 
 lock (typeof(v)) {         // code } 

We assume v is the variable against which you need to synchronize access. Using typeof(v) simultaneously synchronizes access to all instances of v . It can hurt performance because threads might be blocked unnecessarily from simultaneously executing code that actually accesses different instances of the same type, but it is often the simplest solution. In many cases, the only other realistic alternative is to manually hold a boxed copy of v and perform all locks on this boxed copy rather than on v itself ”but this alternative requires keeping careful track of boxed instances, so it is potentially difficult to implement without introducing subtle bugs .

You should be aware that when you pass a variable to a lock statement, what is important is the object that variable points to, not the variable itself. For example, if x and y both happen to refer to the same object, then the following two code statements are completely equivalent statements that have an identical effect on your program:

 
 lock (x) {         // code that accesses x here. } lock (y) {         // code that accesses x here. } 

Comments

Although the syntax involved in locking a variable appears quite simple, you need to take considerable care in where you use the lock statement because there is a lot of potential for subtle thread synchronization bugs if you do not lock variables correctly. It's therefore worth reviewing precisely what a lock statement does.

The CLR internally implements a scheme by which it is able to place markers on objects to indicate that those objects are in use. When the statement lock(x) executes, the CLR first checks whether the object referenced by x is already marked as in use. If it is not marked as in use, then the CLR places a marker on the object and continues execution. This marker is removed when execution flow exits the lock(x) {} block. If, on the other hand, when execution hits the lock(x) statement, the CLR finds that the object referred to by x is already marked as in use by a different thread, then it puts the current thread to sleep, automatically wakes it when x becomes free, puts a new marker on x , and allows execution to continue into the protected block. In this way, we can guarantee that no two protected blocks of code that are locked against the same object will ever be simultaneously executed. Note that the locks are thread-specific: If the same thread executes nested lock(x) statements, then it will not be blocked because the CLR is only interested in preventing different threads from simultaneous access. Also note that locks on different objects are completely independent. Locks placed on any one object have no effect on any lock statements that refer to different objects.

With this understanding of how a lock statement works, we can now point out what to watch when using locks.

First, locking a variable is only effective if you protect all the blocks of code that might access that variable. Suppose you have a block of code that looks like this:

 
 lock (x) {         // do something to x } 

But somewhere else is some code that can be executed by another thread:

 
 // oops! Forgot to put a lock in here x.DoSomethingToChangeItsValue(); 

x is now effectively unprotected because the second block of code does not check the lock on x . It is perfectly possible for both blocks of code to execute simultaneously, and the possibility of thread synchronization bugs arises.

You should also take care to avoid deadlocks. Deadlocks are situations in which threads are cyclically waiting for each other to exit protected blocks, which means all the threads concerned end up indefinitely blocked. A deadlock can happen if one thread executes code like this:

 
 lock(x) {         // protected code         lock (y)         {                 // protected code         } } 

Another thread executes this code:

 
 lock(y) {         // protected code         lock (x)         {                 // protected code         } } 

With this code is the risk that the first thread will place a lock on x at about the same time that the second thread places a lock on y . When the first thread hits the lock(y) statement, it blocks until the second thread releases its lock on y . However, that will never happen because the second thread is waiting for the first thread to release its lock on x ! There are two ways to avoid deadlocks: You can either make sure that you write code that always places nested locks on variables in the same order, or you can use mutexes to obtain locks on multiple objects simultaneously, as discussed later in this chapter.

On the other side are situations in which it is not necessary to lock objects. Most notably, this situation arises with immutable objects. There is no need, for example, to ever protect code against simultaneous access to strings because System.String is immutable. Synchronization bugs can only occur if one thread might actually change the contents of an object while another thread is reading or writing to it. Because the contents of a string can never be changed, strings cannot normally be affected by thread synchronization bugs.

Under the hood, the lock statement is converted by the C# compiler into code that uses the class System.Threading.Monitor . This class is responsible for handling the thread synchronization. You can invoke Monitor methods explicitly if you prefer, but it's almost invariably simpler to use the lock statement and let the C# compiler take care of the implementation details. Although Monitor is not the only thread synchronization primitive, it is by far the most efficient because it is implemented entirely within the CLR. Some of the other thread synchronization classes that we consider in this chapter, including ManualResetEvent , AutoResetEvent , and Mutex , are in wrappers around Windows kernel thread synchronization primitives ”which makes them more powerful in some ways but gives a bigger performance hit.

 <  Day Day Up  >  


Microsoft Visual C# .Net 2003
Microsoft Visual C *. NET 2003 development skills Daquan
ISBN: 7508427505
EAN: 2147483647
Year: 2003
Pages: 440

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