With the introduction of multithreading, you may feel that you need to understand some issues related to thread sharing resources. NET Framework provides many classes 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, you can use the system. threading. the interlocked class completes such a task and provides atomic, non-modular integer update operations.
You can also use the system. Threading. Monitor class to lock an object. Code So that it cannot be accessed by other threads.
An instance of the system. Threading. waithandle class can be used to encapsulate OS-specific objects waiting for exclusive access to shared resources. Especially for the interoperability of unmanaged code.
System. Threading. mutex is used to synchronize multiple complex threads. It also allows single-threaded access.
Synchronization event classes such as manualresetevent and autoresetevent support a thread that notifies other events.
If we do not discuss thread synchronization, we do not know much about multi-threaded programming, but we should be very careful when using multi-threaded synchronization. When using thread synchronization, we must be able to correctly determine in advance that the object and method may cause a deadlock (deadlock means that all threads have stopped the corresponding, are waiting for the other party to release resources ). There is also the issue of dirty data (which refers to the inconsistency caused by operations on data by multiple threads at the same time). This is not easy to understand. Let's just say, there are two threads X and Y. Thread X reads data from the file and writes data to the data structure. Thread y reads data from the data structure and sends the data to other computers. Assuming that X writes data while y reads data, the data read by Y is obviously inconsistent with the data actually stored. We should avoid this situation. A small number of threads will make the problem happen less often, and the access to shared resources will be better synchronized.
The. NET Framework CLR provides three methods to complete shared resources, such as global variable domains, specific code segments, static and instantiated methods and domains.
(1) code domain synchronization: The monitor class can be used to synchronize all or part of code segments of static/instantiated methods. Static domain synchronization is not supported. In the instantiation method, this pointer is used for synchronization, while in static methods, classes are used for synchronization, which will be discussed later.
(2) manual synchronization: use different synchronization classes (such as waithandle, mutex, readerwriterlock, manualresetevent, autoresetevent, and interlocked) to create their own synchronization mechanisms. This synchronization method requires you to manually synchronize different domains and methods. This synchronization method can also be used for inter-process synchronization and deadlock relief caused by waiting for shared resources.
(3) Context synchronization: Use synchronizationattribute to create simple and automatic synchronization for the contextboundobject object. This synchronization method is only used for the synchronization of Instantiation methods and domains. All objects in the same context domain share the same lock.
Monitor class
Only one thread can access the specified code segment at a given time. The monitor class is very suitable for Thread Synchronization in this case. The methods in this class are static, so you do not need to instantiate this class. The following static methods provide a mechanism for synchronizing Object Access to avoid deadlocks and maintain data consistency.
Monitor. Enter method: Obtain the exclusive lock on the specified object.
Monitor. tryenter method: attempts to obtain the exclusive lock of 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 gets the lock again.
Monitor. Pulse Method: notifies the thread in the waiting queue of changing the status of the locked object.
Monitor. pulseall method: notifies all the pending thread object state changes.
You can synchronize the access to the code segment by locking and unlocking the specified object. Monitor. Enter, monitor. tryenter, and monitor. Exit are used to lock and unlock a specified object. Once the lock of the specified object (code segment) is obtained (the monitor. Enter is called), other threads cannot obtain the lock. For example, thread x obtains an object lock, which can be released (Call monitor. Exit (object) or monitor. Wait ). When this object lock is released, the monitor. Pulse method and the monitor. pulseall method notify the next thread of the ready queue to perform the lock and all other threads in the ready queue to obtain the exclusive lock. Thread x releases the lock and thread y obtains the lock. Thread X that calls monitor. Wait enters the waiting queue. When the thread (thread y) of the currently locked object is affected by pulse or pulseall, the thread waiting for the queue enters the ready queue. When thread X Re-obtains the object lock, monitor. Wait returns. If the thread y that owns the lock does not call pulse or pulseall, the method may be locked uncertain. Pulse, pulseall and wait must be the synchronized code segment called. For each synchronization object, you need a pointer to the thread with the current lock, a ready queue, and a waiting queue (including the thread that needs to be notified of the status change of the locked object.
You may ask, what happens when two threads call monitor. Enter at the same time? No matter how close the two threads call monitor. Enter, there must actually be one before, one after, so there will always be only one to get the object lock. Since monitor. Enter is an atomic operation, the CPU cannot be biased towards one thread rather than another. To achieve better performance, you should delay the call to obtain the lock from the next thread and immediately release the object lock from the previous thread. Locking is feasible for private and internal objects, but may lead to deadlocks for external objects, because unrelated code may lock the same object for different purposes.
If you want to lock a piece of code, it is best to add the lock setting statement in the try statement and put monitor. exit in the finally statement. To lock the entire code segment, you can use the methodimplattribute (in the system. runtime. compilerservices namespace) class to set the synchronization value in its constructor. This is an alternative method. When the locking method returns, the lock is released. If you need to release the lock quickly, you can use the Statement of the monitor class and C # Lock to replace the above method.
Let's look at a piece of code that uses 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 code above may cause problems. When the code runs to int c = A/B;, an exception is thrown, and monitor. Exit will not return. Therefore Program Will be suspended, and other threads will not be locked. There are two ways to solve the problem above. The first method is to put the code into try... In finally, when finally calls monitor. Exit, the lock will be released. The second method is to use the lock () method of C. Calling this method has the same effect as calling monitoy. Enter. However, once the code execution exceeds the permitted range, the release lock will not automatically occur. See the following code:
Public void some_method ()
{
Int A = 100;
Int B = 0;
Lock (this );
// Say we do something here.
Int c = A/B;
}
The C # Lock statement provides the same functionality as monitoy. Enter and monitoy. Exit. This method is used when your code segment cannot be interrupted by other independent threads.
Waithandle class
The waithandle class is used as the base class, which allows multiple waiting operations. This class encapsulates the Win32 synchronous processing method. The waithandle object notifies other threads that they need exclusive access to resources. Other threads must wait until waithandle no longer uses resources and the waiting handle is not used. The following are several classes inherited from it:
Mutex: A synchronization primitive can also be used for inter-process synchronization.
Autoresetevent: notifies one or more waiting threads that an event has occurred. This class cannot be inherited.
Manualresetevent: when one or more pending thread events are notified. This class cannot be inherited.
These classes define some signal mechanisms to possess and release exclusive access to resources. They have two statuses: signaled and nonsignaled. The waiting handle in the signaled state does not belong to any thread unless it is in the nonsignaled state. The set method is no longer used by threads that have a waiting handle. Other threads can call the reset method to change the State or any waithandle method requires a waiting handle. The methods are as follows:
Waitall: waits for all elements in the specified array to receive signals.
Waitany: waits for any element in the specified array to receive a signal.
Waitone: when being rewritten in a derived class, the current thread is blocked until the current waithandle receives the signal.
These wait Methods block threads until one or more synchronization objects receive signals.
The waithandle object encapsulates the operating system-specific objects waiting for exclusive access to the shared resources. Both the managed code and the unmanaged code can be used. However, it is not easy to use monitor. Monitor is fully managed code and is very efficient for operating system resources.
Mutex class
Mutex is another method for synchronizing between threads and across processes. It also provides synchronization between processes. It allows a thread to exclusively share resources while blocking access from other threads and processes. The mutex name demonstrates the exclusive possession of resources by its owner. Once a thread has mutex, all other threads that want to get mutex will be suspended until the occupying thread releases it. The mutex. releasemutex method is used to release mutex. A thread can call the wait method multiple times to request the same mutex, but the same number of mutex. releasemutex must be called when the mutex is released. If no thread occupies mutex, the mutex state changes to signaled; otherwise, it is nosignaled. Once the mutex status changes to signaled, wait for the next thread in the queue to get the mutex. The mutex class corresponds to the createmutex of Win32. The method for creating a mutex object is very simple. The following methods are commonly used:
A thread can obtain mutex ownership by calling waithandle. waitone, waithandle. waitany, or waithandle. waitall. If mutex does not belong to any thread, the above call will make the thread have mutex, and waitone will return immediately. However, if other threads have mutex, waitone will wait for an indefinite period until mutex is obtained. You can specify a parameter in the waitone method, that is, the waiting time, to avoid waiting for mutex indefinitely. Calling close to act on mutex will release the ownership. Once mutex is created, you can use the gethandle method to obtain the mutex handle and use it for the waithandle. waitany or waithandle. waitall methods.
The following 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;
Firstmutex. Close ();
}
In the preceding example, the thread creates a mutex, but does not declare its ownership at the beginning. by calling the waitone method, the thread owns the mutex.
Synchronization events
The synchronization time is the waiting handle used to notify other threads of what happened and the resources are available. They have two statuses: signaled and nonsignaled. Autoresetevent and manualresetevent are synchronization events.
Autoresetevent class
This class can notify one or more threads of an event. When a waiting thread is released, it converts the status to signaled. Use the set method to change its instance status to signaled. However, once the notification time of the waiting thread changes to signaled, its turntable will automatically change to nonsignaled. If no thread listens for the event, the turntable will remain signaled. This class cannot be inherited.
Manualresetevent class
This class is also used to notify one or more thread events. Its status can be set and reset manually. The manual reset time will remain in the signaled state until manualresetevent. Reset sets the status to nonsignaled or nonsignaled until manualresetevent. Set sets the status to signaled. This class cannot be inherited.
Interlocked class
It provides synchronization of shared variable access between threads. Its operations are atomic and shared by threads. you can use interlocked. increment or interlocked. decrement to increase or decrease shared variables. it is a bit like an atomic operation. That is to say, these methods can generate an integer parameter increment and return a new value. All operations are one step. you can also use it to specify the value of a variable or check whether the two variables are equal. If they are equal, the specified value will replace the value of one of the variables.
Readerwriterlock class
It defines a lock that provides a unique write/Multi-read mechanism for read/write synchronization. any number of threads can read data. Data locks are required when data is updated by threads. the read thread can obtain the lock only when there is no written thread. when there is no read thread or other write threads, the write thread can get the lock. therefore, once writer-lock is requested, all read threads cannot read data until the write thread completes access. it supports pausing to avoid deadlocks. it also supports nested read/write locks. the nested read lock is supported by readerwriterlock. acquirereaderlock: If a thread has a write lock, the thread will be suspended;
The nested write lock is supported by readerwriterlock. acquirewriterlock. If a thread has a read lock, the thread is suspended. if there is a read lock, it will easily be a deadlock. the safe method is to use readerwriterlock. upgradetowriterlock method, which will upgrade the reader to the writer. you can use readerwriterlock. the downgradefromwriterlock method degrades the writer to a reader. call readerwriterlock. releaselock will release the lock, readerwriterlock. restorelock will reload the lock status to call readerwriterlock. before releaselock.
Conclusion:
This section describes the issue of thread synchronization on the. NET platform. Article I will give some examples to further illustrate these methods and techniques. although the use of thread synchronization will bring great value to our program, we 'd better be careful with these methods. otherwise, the benefits will not be brought, but performance will decline or even program crashes. only a large number of contacts and experiences can you master these skills. use as few things as possible that cannot be completed or are uncertain blocked in synchronous code blocks, especially I/O operations; use local variables instead of global variables as much as possible; synchronization is used in places where some code is accessed by multiple threads and different states are shared by different processes; arranging your code to precisely control each data in a thread; code that is not shared among threads is secure. In the next article, we will learn about thread pools.
If you have carefully read the first three articles, I believe you are correct.. NET Framework. threading. the thread class and some basic thread knowledge and multi-thread programming knowledge of thread synchronization classes are very familiar. We will further discuss some. Net classes here, as well as their roles in multi-threaded programming and how to program them. They are:
System. Threading. threadpool class
System. Threading. Timer class
If the number of threads is not large, and you want to control the details of each thread, such as the thread priority, it is more appropriate to use the thread; but if there are a large number of threads, it should be better to use the thread pool. It provides an efficient thread management mechanism to process multiple tasks. It is suitable for the Timer class of scheduled execution tasks, and usage indicates that it is the first choice for Asynchronous Method calls.
System. Threading. threadpool class
When you create an application, you should realize that most of the time your thread is idle waiting for some events (such as pressing a key or listening for requests from a node ). Without a doubt, you will think this is an absolute waste of resources.
If many tasks need to be completed and each task requires a thread, you should consider using the thread pool to manage your resources more effectively and benefit from it. A thread pool is a collection of multiple threads that are executed. It allows you to add tasks automatically created and started by threads to the queue. Using the thread pool allows your system to optimize the time fragmentation of threads during CPU usage. But remember that at any specific point in time, each process and each thread pool have only one running thread. This class enables the system to manage the pool composed of your threads, so that your main focus is on workflow logic rather than thread management.
When the threadpool class is instantiated for the first time, the thread pool is created. It has a default upper limit, that is, each processor can have up to 25, but this upper limit can be changed. In this way, the processor will not be idle. If one of the threads waits for an event, the thread pool initializes another thread and puts it into processing, the thread pool is the way in which jobs are constantly created and tasks are allocated to threads in the queue that are not working. The only limit is that the number of worker threads cannot exceed the maximum allowed number. Each thread will run at the default priority and use the default stack size space that belongs to the multi-thread space. Once a job is added to the queue, you cannot cancel it.
The queueuserworkitem method can be called to request the thread pool to process a task or work item. This method includes a waitcallback parameter of the type, which encapsulates the task completed by your medicine. The runtime automatically creates a thread for each task and releases the thread when the task is released.
The following code describes how to create a thread pool and add tasks:
Public void afunction (Object O)
{
// Do what ever the function is supposed to do.
}
// Thread entry code
{
// Create an instance of waitcallback
Waitcallback mycallback = new waitcallback (afunction );
// Add this to the thread pool/queue a task
Threadpool. queueuserworkitem (mycallback );
}
You can also call the threadpool. registerwaitforsingleobject method to pass a system. Threading. waithandle. When the notification or time exceeds the time of calling the method encapsulated by system. Threading. waitortimercallback.
The thread pool and event-based programming mode make the thread pool monitor registered waithandles and the appropriate waitortimercallback to indicate that method calls are very simple (when waithandle is released ). These practices are actually very simple. Here, a thread constantly observes the status of waiting for operations in the thread pool queue. Once the operation is completed, a thread is executed with the corresponding task. Therefore, this method adds a thread as the trigger event occurs.
Let's take a look at how to add a thread to the thread pool with the event, which is actually very simple. We only need to create a manualresetevent class event and a waitortimercallback representative. Then we need an object that carries the status, and we also need to determine the break interval and execution method. We add all of the above to the thread pool and trigger an event:
Public void afunction (Object O)
{
// Do what ever the function is supposed to do.
}
// Object that will carry the status info? O: P>
Public class anobject
{
}
// Thread entry code
{
// Create an event object?
Manualresetevent aevent = new manualresetevent (false );
// Create an instance of waitortimercallback
Waitortimercallback thread_method = new waitortimercallback (afunction );
// Create an instance of anobject
Anobject myobj = new anobject ();
// Decide how thread will perform
Int timeout_interval = 100; // timeout in Milli-seconds.
Bool onetime_exec = true;
// Add all this to the thread pool.
Threadpool. registerwaitforsingleobject (aevent, thread_method, myobj, timeout_interval, onetime_exec );
// Raise the event
Aevent. Set ();
}
In the queueuserworkitem and registerwaitforsingleobject methods, the thread pool creates a background thread to call back and forth. When the thread pool starts executing a task, both methods merge the caller's stack into the thread stack of the thread pool. If security checks are required, it will take more time and increase the burden on the system. Therefore, you can avoid security checks by using their corresponding insecure methods. It is threadpool. unsaferegisterwaitforsingleobject and threadpool. unsafequeueuserworkitem.
You can also queue tasks unrelated to the waiting operation. Timer-queue timers and registered wait operations also use thread pools. Their return methods are also put into the thread pool queue.
The thread pool is very useful and widely used. NET platform, waiting for operation registration, process timer and asynchronous I/O. For small and short tasks, the mechanism provided by the thread pool is also very convenient in multithreading. The thread pool is very convenient for completing many independent tasks without setting thread attributes one by one. However, you should also be clear that there are many situations where you can use other methods to replace the thread pool. For example, you plan a task or give specific attributes to each thread, or you need to put the thread into the space of a single thread (and the thread pool is to put all the threads into a multi-thread space ), or a specific task is very lengthy. In these cases, you 'd better consider clearly that the security method should be your choice over using the thread pool.
System. Threading. Timer class
The timer class is very effective for periodically executing tasks in separated threads and cannot be inherited.
This class is especially used to develop console applications, because system. Windows. Forms. Time is unavailable. For example, backup files and check Database Consistency.
When you create a timer object, you can estimate the time between the waiting time before the first proxy call and the time between each successful call. A scheduled call occurs in the time that the method takes, and periodically calls this method later. You can adapt to the change method of timer to change the value of these settings or make timer invalid. When the timer is no longer used, you should call the dispose method to release its resources.
Timercallback specifies the method (the task to be periodically executed) and status associated with the timer object. It calls the method once after the time it deserves, and periodically calls the method until the dispose method is called to release all the resources of the timer. The system automatically allocates separate threads.
Let's look at a piece of code to see how to create a timer object and use it. First, we need to create a timercallback proxy, which will be used in subsequent methods. If necessary, create a State object that has specific information associated with the method called by the proxy. To make these simple, we pass an empty parameter. We will instantiate a timer object, then use the change method to change the timer settings, and finally call the dispose method to release resources.
// Class that will be called by the timer
Public class workontimerreq
{
Public void atimercallmethod ()
{
// Does some work?
}
}
// Timer creation Block
{
// Instantiating the class that gets called by the timer.
Workontimerreq anobj = new workontimerreq ();
// Callback delegate
Timercallback tcallback = new timercallback (anobj. atimercallmethod );
// Define the duetime and Period
Long dtime = 20; // wait before the first tick (in MS)
Long ptime = 150; // timer during subsequent invocations (in MS)
// Instantiate the timer object
Timer atimer = new timer (tcallback, null, dtime, ptime );
// Do some thing with the timer object
// Change the duetime and period of the timer
Dtime = 100;
Ptime = 300;
Atimer. Change (dtime, ptime );
// Do some thing
Atimer. Dispose ();
}
Asynchronous programming
If you want to clarify this part, it is a huge part. Here, I am not going to discuss it in detail, we just need to wait until it is what it is, multi-thread programming is obviously not appropriate if asynchronous multi-thread programming is abnormal. Asynchronous multi-thread programming is another multithreaded programming method that your program may use.
In the previous article, we spent a lot of time introducing thread synchronization and how to implement thread synchronization. However, it has an inherent fatal drawback. You may have noticed this. That is, each thread must make a synchronous call, that is, wait until other functions are completed, otherwise it will be blocked. Of course, in some cases, it is sufficient for logically dependent tasks. Asynchronous programming allows more complex flexibility. A thread can be called asynchronously without waiting for anything else. You can use these threads to execute any task, and the threads are responsible for obtaining the results and promoting the running. This gives enterprise-level systems that need to manage a large number of requests and are unable to afford the cost of waiting for requests for better scalability.
The. NET platform provides consistent asynchronous programming mechanisms for ASP. NET, I/O, web services, networking, and message.
Postscript
Since it is difficult to find Chinese materials during study, I had to learn English materials. Due to the low level, the meaning of the original text may be misinterpreted during translation, I hope you can point out that, at the same time, we hope that these things will give you some reference and help in learning this knowledge. Even a little bit, I am very pleased.
Mao's nest