.NET multi-threaded programming (3): thread synchronization
With the in-depth learning of multi-threaded learning, you may think you need to understand some questions about thread sharing resources. .NET Framework provides a lot of class and data types to control access to shared resources.
Consider a situation we often encounter: There are some global variables and shared class variables, we need to update them from different threads, which can complete such tasks by using the System.Threading.Interlocked class, it provides atoms, Non-modular integer update operations.
There is also a piece of code you can lock the object using the System.Threading.Monitor class, making it temporarily accessible by other threads.
Examples of the System.Threading.Waithandle class can be used to package an operating system-specific object waiting for exclusive access to shared resources. Especially for interoperability issues for non-tube codes.
System.Threading.mutex is used to synchronize multiple complex threads, which also allows single-threaded access.
Synchronous event classes like ManualRestEvent and AutoreteTevent support a class to notify other events.
Do not discuss the synchronization problem of threads, is equal to very little multi-threaded programming, but we must use multiple thread synchronization. When using thread synchronization, we must be able to determine correctly in advance is that objects and methods may cause deadlocks (dead locks are all threads to stop corresponding, all of them release resources). There is also a problem with the data (referring to the inconsistency caused by multiple threads on the same time), this is not easy to understand, so, there are X and Y two threads, thread x read data from the file And write data to the data structure, thread y is read from this data structure and send data to another computer. Assuming that the Y read data is written, the data is obvious that the data read from the actual store is inconsistent. This situation is obviously what we should avoid happening. A small number of threads will make more than a few more probabilities, and more good synchronization for shared resources.
The CLR of the .NET Framework provides three ways to complete the shared resource, such as a global variable domain, a specific code segment, static, and instantiated method and domain.
(1) Code domain synchronization: Use the Monitor class to synchronize all code or part of the method of the static / instantiated method. Static domain synchronization is not supported. In an instantiated method, the THIS pointer is used for synchronization; and in a static method, the class is used for synchronization, which will be described later.
(2) Handmade synchronization: Create your own synchronization mechanism using different synchronous classes (such as WaitHandle, Mutex, ReaderWriterlock, ManualRetevent, AutoreteTevent, and Interlocked, etc.). This synchronous mode requires you to manually synchronize different domains and methods, which can also be used for synchronization between processes and release of deadlocks caused by waiting for the shared resource.
(3) Context synchronization: Use SynchronizationAttribute to create simple and automatic synchronization for the ContextBoundObject object. This synchronization method is only used for instantiated methods and domain synchronization. All objects share the same lock in the same top and bottom field.
Monitor Class
Only one thread can be accessed at a given time and the specified code segment, and the Monitor class is very suitable for thread synchronization of this situation. The methods in this class are static, so do not instantiate this class. Some static methods below provide a mechanism to synchronize access to the consistency of deadlock and maintain data.
Monitor.Enter Method: Gets his lock on the specified object.
Monitor.TryEnter method: Try to get his lock in the specified object. Monitor.exit method: Release the exclusive lock on the specified object.
Monitor.wait method: Release the lock on the object and block the current thread until it reaches the lock.
Monitor.pulse method: Notify the wait for the change in the thread lock object status in the queue.
Monitor.pulseAll method: Notify all changes to the status of the thread object state.
You can synchronize the code segment by locking and unlocking the specified object. Monitor.Enter, Monitor.Tryenter and Monitor.exit are used to lock and unlock the specified object. Once you get (call Monitor.Enter) specify the lock of the object (code segment), the other threads cannot get the lock. For example, thread x get an object lock, this object lock can be released (call Monitor.exit (Object) or Monitor.Wait). When this object lock is released, the Monitor.pulse method and Monitor.pulseall method notify the next thread of the ready queue and all other ready queues will have the opportunity to get his lock. The thread x releases the lock and thread y gets the lock while calling the Monitor.wait thread x into the waiting queue. When the thread (thread y) from the currently locked object is received by Pulse or Pulseall, the thread waiting for the queue will enter the ready queue. The thread x returns when the object is reacted. If a locking thread (thread y) does not call Pulse or PulseAll, the method may be locked in uncertain. Pulse, Pulseall and Wait must be called synchronized code segments. For each synchronized object, you need a pointer to the pointer, ready queue and waiting queue (including a thread that need to be notified by notifying the status of the object).
You may ask, what happens when two threads call Monitor.Enter? Regardless of the two threads to call Monitor.Enter, it is actually one before, one afterward, there will never have an object lock. Since Monitor.Enter is atomic operation, the CPU is impossible to prejug, do not like another thread. In order to get better performance, you should delay the acquisition of the latter to get the lockup and immediately release the front thread object lock. For Private and Internal objects, the lock is possible, but it is possible to cause a deadlock for the External object, because the unrelated code may lock the same object due to different purposes.
If you want to lock a code, it is best to add a set lock statement in the TRY statement, and put Monitor.exit in the Finally statement. For locking of the entire code segment, you can use MethodImplattribute (in the System.Runtime.CompilerServices Namespace) class to set the synchronization value in its constructor. This is an alternative method that the lock is released when the method is turned back. If you need to release the lock quickly, you can use the Monitor class and C # Lock's declaration instead of the above method.
Let's take a look at the code using the Monitor class:
Public void some_method () {
INT A = 100;
INT b = 0;
Monitor.enter (this);
// Say we do something here.
INT C = A / B;
Monitor.exit (this);
}
The above code will generate a problem. When the code is running to INT C = A / B; will throw an exception, Monitor.exit will not return. So this program will hang, and other threads will not be locked. There are two ways to solve the above problems. The first method is to put the code into the try ... finally, call Monitor.exit in Finally, so that the lock will be released. The second method is to use the LOCK () method of the C #. The effect of calling this method and calling the Monitoy.Enter is the same. However, once the code performs an outward range, the release lock will not occur automatically. See the code below: public void some_method () {
INT A = 100;
INT b = 0;
LOCK (this);
// Say we do something here.
INT C = A / B;
}
C # Lock stated that the same feature as Monitoy.Enter and Monitoy.exit, which is used in your code segment that cannot be interrupted by other independent threads.
Waithandle Class
The WaitHandle class is used as a base class, which allows multiple waiting to be operated. This class encapsulates the synchronization method of Win32. WaitHandle objects Notify other threads It requires access to resource exclusive access, and other threads must wait until WaitHandle no longer uses resources and waiting for handles. Below is a few classes from it inherited:
MuteX class: The synchronization primitive can also be used in inter-process synchronization.
AutoreteTevent: Informs one or more threads that have occurred have occurred. Unable to inherit this class.
ManualRestEvent: When notifies one or more thread events that are waiting for it already occurs. Unable to inherit this class.
These classes define some signaling mechanisms to make and release the resource exclusive access. They have two states: Signaled and Nonsignaled. The wait handle of the Signaled state is not part of any thread unless the Nonsignaled state. The thread of the waiting handle no longer uses the SET method when you wait for the handle, and other threads can call the reset method to change the status or any WaitHandle method requires waiting for handle, which see below:
Waitall: Wait for all elements in the specified array to receive the signal.
WAITANY: Wait for either element in the specified array to receive the signal.
Waitone: When you override in the derived class, block the current thread until the current WaitHandle receives the signal.
These WAIT methods block the thread until one or more synchronous objects receive signals.
WaitHandle Object Package Wait for an operating system for exclusive access to shared resources, whether it is a receiving code or non-managed code. But it doesn't have Monitor to use light, Monitor is a complete managed code and is very efficient to the use of operating system resources.
Mutex Class
Mutex is another way to complete the thread and cross-process synchronization, and it also provides synchronization between processes. It allows a thread exclusively to block access from other threads and processes while sharing resources. The name of Mutex is very good to illustrate its owner's exclusive possession of resources. Once a thread has Mutex, you want to get other threads of MUTEX will hang until the thread is released. Mutex.releaseMutex method is used to release Mutex, and a thread can call the wait method multiple times to request the same MUTEX, but Mutex.ReleaseMutemutemute that must be called when the Mutex must call. If there is no thread occupies Mutex, then the state of MUTEX becomes Signaled, otherwise it is NOSIGNALED. Once the state of Mutex changes to Signaled, the next thread waiting for the queue will get MUTEX. The Mutex class corresponds to the Win32's CreateMutex. It is very simple to create a Mutex object. It is common to use the following methods: A thread can get Mutex's ownership by calling WaitHandle.WaitAny or WaitHandle.Waitall. If Mutex does not belong to any thread, the above call will make the thread possex, and Waitone will return immediately. But if there are other threads with Mutex, Waitone will fall into the indefinite waiting until you get Mutex. You can specify a parameter waiting time in the waitone method to avoid an indefinite period MUTEX. Call the Close acting on Mutex will be released. Once Mutex is created, you can use the GetHandle method to get the Mutex handle to use the Waithandle.Waitany or WaitHandle.Waitall method.
Here is an example:
Public void some_method () {
INT A = 100;
INT b = 20;
Mutex firstmutex = new mutex (false);
Firstmutex.waitone ();
// Some Kind of Processing Can Be Done Here.
INT x = a / b;
FigStmutex.close ();
}
In the above example, the thread creates Mutex, but starting without stating that it has MUTEX by calling the waitone method.
Synchronization Events
Synchronization time is some wait handle to notify other threads what happens and resources are available. They have two states: Signaled and Nonsignaled. AutoreteTevent and ManualReveTevent are this synchronization event.
AutoreteEvent Class
This class can notify one or more threads. When a waited thread is released, it converts the status to Signaled. Use the SET method to make its instance status to signal. However, once the wait thread is notified to signal to signal, its turntable will automatically change to Nonsignaled. If there is no thread listening event, the turntable will remain in Signaled. Such can't be inherited.
ManualReveTevent Class
This class is also used to inform one or more thread events. Its state can be manually set and reset. Manual reset time will keep the Signaled state until the ManualReveTevent.reset sets its status as nonsignaled, or keeps the status as nonsignaled until the manualReveTevent.set sets its status as signalted. This class cannot be inherited.
Interlocked Class
It provides synchronization of variable access shared between threads, its operation, atomic operation, and thread sharing. You can add or reduce shared variables via interlocked.increment or interlocKed.Decrement. It is a bit of atomic operations That means that these methods can be incrementally incremented by a constant parameter and return a new value, all the operation is a step. You can also use it to specify the value of the variable or check if the two variables are equal, if equally, The specified value replaces the value of one of the variables. ReaderWriterlock Class
It defines a lock that provides a unique write / multi-read mechanism that makes read and write synchronization. All number threads can read data, and the data lock will be needed when there is a thread update data. Read threads can get locks And only the thread that is not written here. When there is no reading thread and other write threads, the write thread can be locked. Therefore, once the Writer-Lock is requested, all read threads will not read the data until Write thread access It is completed. It supports pause and avoids dead lock. It also supports nested read / write locks. The method of supporting nested readlock is ReaderWriterLock.AcquireReaderLock, if a thread is locked, the thread will be paused;
The method of supporting nested write is ReaderWriterlock.AcquireWriterlock, if a thread is read, the thread is paused. If a read lock will be easy to die. The security method is to use the ReaderWriterlock.Upgradetowriterlock method, which will enable the reader to upgrade to the reader Writer. You can use the ReaderWriterlock.DowngradeFromWriterlock method to degrade the writer to the reader. Call ReaderWriterLock.Releaselock will release the lock, ReaderWriterlock.RestoreLock will reload the status to call ReaderWriterlock.ReleaseLock.
in conclusion:
This part tells the problem of thread synchronization on the .NET platform. In the series, I will give some examples to further illustrate the methods and techniques of these uses. Although thread synchronization will give us a program Come big value, but we'd better use these methods. Otherwise, it is not benefiting, but will be performance drops and even the program crash. Only a lot of contacts and experience can make you control these skills. Use less as little as possible Synchronous code blocks do not complete or uncertain blocking things, especially I / O operation; use local variables as much as possible instead of global variables; synchronization is used in those partial code and process access and status by multiple threads and processes Sharing place; arrange your code to get accurate control in a thread; not the code between the thread is secure; in the next article, we will learn the knowledge of thread pool.