Reading the same series: multithreading: C # thread synchronization lock, monitor, mutex, synchronization event and wait handle (on), multithreading: C # thread synchronization lock, monitor, mutex, synchronization event and wait handle (medium)
The first two articles briefly introduce the basic usage of thread synchronization lock, monitor, synchronization event eventwaithandler and mutex. On this basis, we will compare their usage, it also provides some suggestions on when to lock and when not needed. Finally, we will introduce several thread-safe classes in FCL and the locking methods of collection classes to improve and supplement the thread synchronization series.
1. Differences between several Synchronization Methods
Lock and monitor are implemented by. net in a special structure. The monitor object is fully hosted and portable, and may be more effective in terms of operating system resource requirements,Fast synchronization,Cross-process synchronization is not allowed. Lock (encapsulation of the monitor. Enter and monitor. Exit methods), mainly used to lock the critical section, so that the critical sectionCodeIt can only be executed by the thread that gets the lock. Monitor. Wait and monitor. Pulse are used for thread synchronization. They are similar to signal operations. I personally feel that they are complicated to use and may cause deadlocks.
Mutex and event object eventwaithandler are kernel objects. When using kernel objects for thread synchronization, threads must switch between user and kernel modes.Low EfficiencyBut with kernel objects such as mutex objects and event objects, you canSynchronize between threads in multiple processes.
Mutex is similar to a baton. The thread that obtains the baton can start running. Of course, the baton only belongs to one thread (thread affinity) at a time. If this thread does not release the baton (mutex. releasemutex), so there is no way, all other threads that need to run the baton know that they can wait to watch the fun.
The eventwaithandle class allowsThreads communicate with each other by sending signals. Generally, one or more threads are blocked on eventwaithandle until an unblocked thread calls the Set Method to release one or more blocked threads.
2. When to lock
First, we must understand that locking solves the competition condition, that is, multiple threads access a resource at the same time, resulting in unexpected results. For example, the simplest case is that if one counter and two threads add one at the same time, the result is that one count is lost, but frequent locks may cause performance consumption, there are also terrible deadlocks. So under what circumstances do we need to use locks and under what circumstances do we not need them?
1) only shared resources need to be locked
Only shared resources that can be accessed by multiple threads need to be locked, such as static variables, and some cached values. variables inside the thread do not need to be locked.
2) use more lock and less mutex
If you must use a lock, try not to use the lock mechanism of the kernel module, for example. net mutex, semaphore, autoresetevent and manuresetevent. Using such a mechanism involves switching between the user mode and the kernel mode, resulting in a lot of poor performance, but their advantage is that they can synchronize threads across processes, so we should be clear about their differences and applicability.
3) Understand yourProgramHow is it running?
In fact, most of the logic in Web development is expanded in a single thread, and a request is processed in a separate thread. Most of the variables belong to this thread, there is no need to consider locking, of course for ASP. net, we need to consider locking the data in the Application object.
4) give the lock to the database
In addition to data storage, a database also has an important purpose: synchronization. The database uses a complex mechanism to ensure data reliability and consistency, which saves us a lot of energy. With the synchronization on the data source header ensured, most of our effort can be concentrated on the synchronous access to cache and other resources. Generally, locking is considered only when multiple threads are involved in modifying the same record in the database.
5) business logic requirements on transaction and thread security
This is the most fundamental thing. It is time-consuming and laborious to develop programs with full thread security. In cases involving financial systems such as e-commerce, many logics must be strictly thread-safe, so we have to sacrifice some performance and a lot of development time to do this. In general applications, although the program is in the risk of competition in many cases, we can still do not use locking. For example, in some cases, if one or more counters are missing and the results are harmless, we can leave it alone.
3. Interlocked class
The interlocked class provides synchronous access to variables shared by multiple threads. If the variable is in the shared memory, the thread of different processes can use this mechanism. The lock operation is atomic, that is, the entire operation cannot be interrupted by another lock operation on the same variable. This is very important in the preemptive multi-threaded operating system. In such an operating system, A thread can be suspended after a value is loaded from a memory address but before it can be changed or stored.
Let's look at an example of interlock. increment (). This method increments the specified variable in the form of atoms and stores the results. The example is as follows:
Example of increment () method Accumulation
Class Interlockedtest
{
Public Static Int64 I = 0 ;
Public Static Void Add ()
{
For ( Int I = 0 ; I < 100000000 ; I ++ )
{
Interlocked. increment ( Ref Interlockedtest. I );
// Interlockedtest. I = interlockedtest. I + 1;
}
}
Public Static Void Main ( String [] ARGs)
{
Thread T1 = New Thread ( New Threadstart (interlockedtest. Add ));
Thread T2 = New Thread ( New Threadstart (interlockedtest. Add ));
T1.start ();
T2.start ();
T1.join ();
T2.join ();
Console. writeline (interlockedtest. I. tostring ());
Console. Read ();
}
}
Output result 200000000: If the interlockedtest. Add () method replaces the interlocked. increment () method with a comment-out statement, the result is unpredictable and the execution result is different each time. The interlockedtest. Add () method ensures the atomicity of the add 1 operation. The function is equivalent to automatically using the lock for the add operation. At the same time, we also noticed that interlockedtest. Add () is much time-consuming than adding 1 with the + sign directly. Therefore, the lock resource loss is obvious.
In addition, the interlockedtest class has several common methods. For specific usage instructions, refer to the introduction on msdn.
4. Synchronization of collection classes
. NET provides a Lock Object syncroot in some collection classes, such as queue, arraylist, hashtable, and stack. The source code for viewing the syncroot attribute (stack. synchroot is slightly different) with reflector is as follows:
Source code of syncroot attributes
Public Virtual Object Syncroot
{
Get
{
If ( This . _ Syncroot = Null )
{
// If _ syncroot and null are equal, assign the new object to _ syncroot.
// The interlocked. compareexchange method ensures that multiple threads are thread-safe when syncroot is used.
Interlocked. compareexchange ( Ref This . _ Syncroot, New Object (), Null );
}
Return This . _ Syncroot;
}
}
Here, pay special attention to the following mentioned by msdn:Enumerating a set from beginning to end is not a thread-safe process. Even if a set has been synchronized, other threads can modify the set, which causes an exception in the number of enumerations. To ensure thread security during enumeration, you can lock the set during the enumeration process or catch exceptions caused by changes made by other threads.. Use the following code:
Example of using lock for queue
Queue Q = New Queue ();
Lock (Q. syncroot)
{
Foreach ( Object Item In Q)
{
// Do something
}
}
Another note is that the collection class provides a synchronization-related method synchronized, which returns a corresponding Wrapper class of the Collection class, which is thread-safe, most of his methods use the lock keyword for synchronous processing. For example, if synchronized of hashtable returns a new thread-safe hashtable instance, the Code is as follows:
Use and understanding of synchronized
// In a multi-threaded environment, we only need to instantiate hashtable in the following way.
Hashtable HT = Hashtable. synchronized ( New Hashtable ());
// The following code is implemented by the. NET Framework class library to increase your understanding of synchronized.
[Hostprotection (securityaction. linkdemand, synchronization = True )]
Public Static Hashtable synchronized (hashtable table)
{
If (Table = Null )
{
Throw New Argumentnullexception ( " Table " );
}
Return New Synchashtable (table );
}
// Several common synchashtable methods, we can see that the internal implementation has added the lock keyword to ensure thread security.
Public Override Void Add ( Object Key, Object Value)
{
Lock ( This . _ Table. syncroot)
{
This . _ table. Add (Key, value);
}< BR >}
Public override void clear ()
{< br> lock ( This . _ table. syncroot)
{< br> This . _ table. clear ();
}< BR >}
Public Override VoidRemove (ObjectKey)
{
Lock(This. _ Table. syncroot)
{
This. _ Table. Remove (key );
}
}
thread synchronization is a very complex topic. Here we just sort out the relevant knowledge based on a company project as a summary of our work. What are the application scenarios of these Synchronization Methods? What are the differences? Further study and practice are required.