C # language-multi-thread lock system (1 ),
Introduction
In multi-threaded development, thread synchronization cannot be avoided. This article briefly describes the lock system in net multithreading.
Directory
I. lock and Monitor
1: Basic.
2: scope.
3: string lock.
4: monitor usage
Ii. mutex
Iii. Semaphore
Iv. Summary
I. lock and Monitor1: Basic
Lock is a simplified statement of the Monitor syntax sugar. Lock generates Monitor in IL.
// ======= Example 1 ===== string obj = "helloworld"; lock (obj) {Console. writeLine (obj);} // lock IL will be compiled into the following code: bool isGetLock = false; Monitor. enter (obj, ref isGetLock); try {Console. writeLine (obj);} finally {if (isGetLock) {Monitor. exit (obj );}}
The isGetLock parameter is newly added after Framework 4.0. To enable the program to determine under all circumstances whether it is necessary to release the lock. For example, Monitor. Enter cannot obtain the lock.
Monitor. Enter is a lock value type. The lock is packed into new objects, so thread synchronization cannot be performed.
2: Scope
1. Lock can only be locked within a process and cannot be used across processes.
Ii. type lock. As follows:
// ======= Example 2 ===== new Thread (new ThreadStart () ==>{ lock (typeof (int) {Thread. sleep (1, 10000); Console. writeLine ("Thread1 release ");}})). start (); Thread. sleep (1000); lock (typeof (int) {Console. writeLine ("Thread2 release ");}
The running result is as follows:
Let's look at an example.
// ======= Example 3 ==== Console. writeLine (DateTime. now); AppDomain appDomain1 = AppDomain. createDomain ("AppDomain1"); LockTest Worker1 = (LockTest) appDomain1.CreateInstanceAndUnwrap (Assembly. getExecutingAssembly (). fullName, "leleapplication1.locktest"); Worker1.Run (); AppDomain appDomain2 = AppDomain. createDomain ("AppDomain2"); LockTest Worker2 = (LockTest) appDomain2.CreateInstanceAndUnwrap (Assembly. getExecutingAssembly (). fullName, "leleapplication1.locktest"); Worker2.Run (); /// <summary> /// when cross-application domain boundary or remote access is required, the MarshalByRefObject must be inherited. /// </summary> public class LockTest: Export albyrefobject {public void Run () {lock (typeof (int) {Thread. sleep (1, 10000); Console. writeLine (AppDomain. currentDomain. friendlyName + ": Thread released," + DateTime. now );}}}
The running result is as follows:
The first example shows that the lock type int in the same process in the same domain and in different threads actually locks the same int object. Therefore, use it with caution.
The second example is as follows.
A: When CLR is started, the System Domain and Shared Domain will be created, and the Default AppDomain will be created ). The system domain and shared domain are single-instance. There can be multiple program domains. In this example, we use the AppDomain. CreateDomain method to create one.
B: Normally, the code in each program domain is isolated and does not affect each other. However, for some basic types, each application domain is reloaded, which is a waste and brings additional loss pressure. The smart CLR will load the MSCorLib. dll Assembly of some basic types such as Object, ValueType, Array, Enum, String, and Delegate to the shared domain during CLR startup. Each program domain uses a basic instance of the shared domain.
C: each application domain has its own hosting heap. GC heap and Loader heap are the most important items in the managed heap. GC heap is used for reference-type Instance Storage, lifecycle management, and garbage collection. Loader heap Storage System, such as MethodTable and data structure. The lifecycle of Loader heap is not managed by GC, and is related to uninstalling the program domain.
So the int instance in the shared domain Loader heap MSCorLib. dll will be retained until the process ends. Uninstalling a single program domain is not affected. Very large scope !!!
The second example is easy to understand. The lock int instance is cross-program domain, and the basic types in MSCorLib are all like this. It is easy to cause deadlocks and should be used with caution. The custom type is loaded into your own program domain and will not affect others.
3: string lock
We all know that the purpose of the lock is to destroy values under multiple threads. We also know that string is a special object in c # And the value remains unchanged. Each change is a new object value, which is also the reason why stringbuilder is recommended. Example:
//======Example 4===== string str1 = "mushroom"; string str2 = "mushroom"; var result1 = object.ReferenceEquals(str1, str2); var result2 = object.ReferenceEquals(str1, "mushroom"); Console.WriteLine(result1 + "-" + result2); /* output * True-True */
Because of the character string in c #, strings are not modified and read-only in multiple threads. It exists in a hash table in the managed heap in the SystemDomain domain. Key is the address of the string object, and Value is the address of the string object.
When the program domain needs a string, CLR first tries to find the corresponding Item based on the string hash code in this Hashtable. If it is found, the corresponding reference is directly returned. Otherwise, the string is created in the managed heap corresponding to SystemDomain, added to the hash table, and the reference is returned. Therefore, the life cycle of a string is based on the entire process and cross-AppDomain.
4: monitor usage
This section describes the usage of Wait, Pulse, and PulseAll. With Comments, you can directly read the code.
Static string str = "mushroom"; static void Main (string [] args) {new Thread () => {bool isGetLock = false; Monitor. enter (str, ref isGetLock); try {Console. writeLine ("Thread1 first lock acquisition"); Thread. sleep (1, 5000); Console. writeLine ("Thread1 temporarily releases the lock and waits for other threads to release the notification signal. "); Monitor. Wait (str); Console. WriteLine (" Thread1 is notified, and the second lock is obtained. "); Thread. sleep (1000);} finally {if (isGetLock) {Monitor. exit (str); Console. writeLine ("Thread1 release lock ");}}}). start (); Thread. sleep (1000); new Thread () => {bool isGetLock = false; Monitor. enter (str, ref isGetLock); // wait until other instances are released. Try {Console. WriteLine ("Thread2 get lock"); Thread. Sleep (5000); Monitor. Pulse (str); // notify a Thread in the queue to change the lock status. Pulseall notifies all Console. WriteLine ("Thread2 notifies other threads and changes the status. "); Thread. sleep (1000);} finally {if (isGetLock) {Monitor. exit (str); Console. writeLine ("Thread2 release lock ");}}}). start (); Console. readLine ();
Ii. mutex
The lock cannot be used across process locks. Mutex is similar to lock, but it can lock resources across processes. Let's look at an example.
Static bool createNew = false; // whether the first parameter should have the initial ownership of the mutex. That is, when createNew is true, mutex obtains the processing signal by default // The second is the name and the third is successful. Public static Mutex mutex = new Mutex (true, "mushroom. mutex ", out createNew); static void Main (string [] args) {// ===== Example 5 === = if (createNew) // when the first one is successfully created, the lock is obtained. No more WaitOne. Be sure to pay attention. {Try {Run () ;}finally {mutex. ReleaseMutex (); // release the current lock.} // The WaitOne function stops the current thread until it receives the processing signal released by another instance. // The first parameter is the wait time-out time, and the second parameter is whether to exit the context synchronization domain. Else if (mutex. waitOne (10000, false) // {try {Run ();} finally {mutex. releaseMutex () ;}} else // if no processing signal is found {Console. writeLine ("an instance already exists. "); Console. ReadLine () ;}} static void Run () {Console. WriteLine (" instance 1 "); Console. ReadLine ();}
Let's test the sequence of instance a B. A gets the lock first and outputs instance 1. B is waiting. If A is released within 10 seconds, B gets the execution Run (). An instance is output after the timeout.
Note that the first instance that obtains the processing signal has obtained the lock. No more WaitOne. Otherwise, an exception is reported.
Iii. Semaphore
That is, semaphores. we can regard them as upgraded mutex. Mutex locks a resource, while semaphore locks multiple resources.
Semaphore has a thread counter. Each time a thread calls a counter, the counter is reduced by one. After the counter is released, the corresponding thread counter is added by one. The number of threads is exceeded. Semaphore can also be used across processes.
Static void Main (string [] args) {Console. writeLine ("prepare for processing queue"); bool createNew = false; SemaphoreSecurity ss = new SemaphoreSecurity (); // Semaphore semaphore Semaphore = new Semaphore (2, 2, "mushroom. semaphore ", out createNew, null); for (int I = 1; I <= 5; I ++) {new Thread (arg) => {semaphore. waitOne (); Console. writeLine (arg + "processing"); Thread. sleep (10000); semaphore. release (); // semaphore. release (1) // Semaphore. Release (5); multiple instances can be released, but the maximum value cannot be exceeded. If the total number of last releases exceeds the total number, an error is returned. Not recommended}). Start (I);} Console. ReadLine ();}
Iv. Summary
Mutex and Semaphore have poor performance and need to be used before cross-process operations.
The performance of lock and Monitor is better.
Pay attention to deadlocks.
Reference resources
Http://www.cnblogs.com/artech/archive/2007/06/04/769805.html
Author: Mr. mushroom
Source: http://www.cnblogs.com/mushroom/p/4175286.html