Thread Synchronization and Class Monitor
Often, multiple threads of execution manipulate shared data. If threads with access to shared data simply read that data, then any number of threads could access that data simultaneously and no problems would arise. However, when multiple threads share data and that data is modified by one or more of those threads, then indeterminate results may occur. If one thread is in the process of updating the data and another thread tries to update it too, the data will reflect only the later update. If the data is an array or other data structure in which the threads could update separate parts of the data concurrently, it is possible that part of the data will reflect the information from one thread while part of the data will reflect information from another thread. When this happens, the program has difficulty determining when the data has been updated properly.
The problem can be solved by giving one thread at a time exclusive access to code that manipulates the shared data. During that time, other threads wishing to manipulate the data should be kept waiting. When the thread with exclusive access to the data completes its data manipulations, one of the waiting threads should be allowed to proceed. In this fashion, each thread accessing the shared data excludes all other threads from doing so simultaneously. This is called mutual exclusion or thread synchronization.
C# uses the .NET Framework's monitors to perform synchronization. Class Monitor provides the methods for locking objects to implement synchronized access to shared data. Locking an object means that only one thread can access that object at a time. When a thread wishes to acquire exclusive control over an object, the thread invokes Monitor method Enter to acquire the lock on that data object. Each object has a SyncBlock that maintains the state of that object's lock. Methods of class Monitor use the data in an object's SyncBlock to determine the state of the lock for that object. After acquiring the lock for an object, a thread can manipulate that object's data. While the object is locked, all other threads attempting to acquire the lock on that object are blocked from acquiring the locksuch threads enter the Blocked state. When the thread that locked the shared object no longer requires the lock, that thread invokes Monitor method Exit to release the lock. This updates the SyncBlock of the shared object to indicate that the lock for the object is available again. At this point, if there is a thread that was previously blocked from acquiring the lock on the shared object, that thread acquires the lock to begin its processing of the object. If all threads with access to an object attempt to acquire the object's lock before manipulating the object, only one thread at a time will be allowed to manipulate the object. This helps ensure the integrity of the data.
|
C# provides another means of manipulating an object's lockkeyword lock. Placing lock before a block of code (designated with braces) as in
lock ( objectReference ) { // code that requires synchronization goes here }
obtains the lock on the object to which the objectReference in parentheses refers. The objectReference is the same reference that normally would be passed to Monitor methods Enter, Exit, Pulse and PulseAll. When a lock block terminates for any reason, C# releases the lock on the object to which the objectReference refers. We explain lock further in Section 15.8.
If a thread that owns the lock on an object determines that it cannot continue with its task until some condition is satisfied, the thread should call Monitor method Wait and pass as an argument the object on which the thread will wait until the thread can perform its task. Calling method Monitor.Wait from a thread releases the lock the thread has on the object that Wait receives as an argument and places that thread in the WaitSleepJoin state for that object. A thread in the WaitSleepJoin state of a specific object leaves that state when a separate thread invokes Monitor method Pulse or PulseAll with that object as an argument. Method Pulse TRansitions the object's first waiting thread from the WaitSleepJoin state to the Running state. Method PulseAll transitions all threads in the object's WaitSleepJoin state to the Running state. The transition to the Running state enables the thread (or threads) to get ready to continue executing.
There is a difference between threads waiting to acquire an object's lock and threads waiting in an object's WaitSleepJoin state. Threads that call Monitor method Wait with an object as an argument are placed in that object's WaitSleepJoin state. Threads that are simply waiting to acquire the lock enter the conceptual Blocked state and wait until the object's lock becomes available. Then, a Blocked thread can acquire the object's lock.
Monitor methods Enter, Exit, Wait, Pulse and PulseAll all take a reference to an objectusually keyword thisas their argument.
|
|
|
Producer Consumer Relationship without Thread Synchronization
|