A detailed description of how C # achieves multi-thread synchronization in the. NET Framework

Source: Internet
Author: User

Author: Liu Lei

 

This article mainly describes the thread synchronization method in C. There are a lot of online information about the basic concepts of threads. Thread Synchronization is inevitable in multi-threaded applications. In. in the Net Framework, thread synchronization is implemented mainly through the following methods. several methods have been introduced in the msdn thread guide. This article describes the methods used by the author in practice.

 

1. Maintain the free lock (interlocked) for synchronization

2. Monitor and lock)

3. read/write lock (readwritelock)

4. system kernel objects

1) mutex, semaphore ),
Event (autoresetevent/manualresetevent)

2) thread pool

 

In addition to the above objects, you can also use the thread. Join method to implement thread synchronization. This method is relatively simple. When you want to wait for the execution result of the second thread while the first thread is running, you can join the second thread.

 

Interlocked)

 

Increment and decrease the number of 32-bit integer types to implement the lock. Someone may ask why the operation is not performed using ++ or. This is because the operations on locks in multiple threads Must Be atomic, and ++ and -- do not have this capability. The interlocked class also provides two additional functions: exchange,
Compareexchange is used for exchange and comparison exchange. The exchange operation will set the new value to the variable and return the original value of the variable: int oval =
Interlocked. Exchange (ref Val, 1 ).

 

Monitor)

In msdn, the monitor class grants an object lock to a single thread to control access to the object.

The monitor class is a static class, so you cannot get the Class Object Through instantiation. Monitor members can view msdn. Basically, the effect of monitor is the same as that of lock. Use the lock operation enter to set the critical section. After the operation is completed, use the exit Operation to release the object lock. However, the monitor function is more powerful. moniter can test the lock status. Therefore, you can control the access selection for the critical section and wait for or to exit,
In addition, the monitor can notify the specified object before releasing the lock. More importantly, the monitor can be used to perform operations across methods. The method provided by Monitor is rarely the only method to obtain the lock. Enter,
Tryenter; the lock release method is wait, exit; and The Message notification method is pulse and pulseall. The classic monitor operation is as follows:

 

 

// Create static public void deluser (string name) {try {// wait for the thread to enter monitor. enter (names); names. remove (name); console. writeline ("DEL: {0}", names. count); monitor. pulse (names);} finally {// release the object lock monitor. exit (names );}}}

 

Names is a list,
Here is a tip: If you want to declare the entire method as thread synchronization, you can use the method attributes:

 

 

// Set the entire Method to the critical section [methodimpl (methodimploptions. synchronized)] static public void adduser (string name) {names. add (name); console. writeline ("Add: {0}", names. count );}

There is a strange way to use monitor, that is, the wait method. The description of wait in msdn is:
Release the lock on the object to allow other threads to lock and access the object.

The above mentioned is to release the lock first, so obviously we need to get the lock first. Otherwise, an exception will occur when wait is called. Therefore, we must call the enter method or other methods to obtain the lock before wait, such as lock, this is very important. Corresponding to the enter method, monitor provides another Implementation of tryenter. The main difference between the two methods is whether to block the current thread. When the enter method fails to obtain the lock, it will block the current thread until it gets the lock. But the disadvantage is that if the lock is never obtained, the program will enter the Deadlock State. We can use wait to solve the problem. We can add a timeout period when calling wait.

 

if (Monitor.TryEnter(Names))            {                Monitor.Wait(Names, 1000); // !!                 Names.Remove(name);                 Console.WriteLine("Del: {0}", Names.Count);                Monitor.Pulse(Names);             }

Lock)

The lock keyword is a simple method to implement thread synchronization. In fact, it is to set a critical section. Block {...} after lock is a critical section. when entering the critical section, a mutex lock is applied and the lock is released when it leaves the critical section. Msdn describes the lock keyword as follows:
The lock keyword can mark the statement block as a critical section by obtaining the mutex lock of a given object, executing the statement, and releasing the lock.

An example is as follows:

 

static public void ThreadFunc(object name)        {            string str = name as string;            Random rand = new Random();            int count = rand.Next(100, 200);            for (int i = 0; i < count; i++)            {                lock (NumList)                {                    NumList.Add(i);                    Console.WriteLine("{0} {1}", str, i);                }            }        }

There are several suggestions for using lock: Lock (this) on the instance and lock (typeof (VAL) on the static variable )). The access permission of the lock object should be private; otherwise, access control will be lost.

Read/write lock (readwritelock)

The emergence of read/write locks is mainly in many cases, we read more resources than write operations. However, it is obviously inefficient to grant only one thread access permission to resources at a time. The advantage of the read/write lock is that multiple threads can read the same resource at the same time. Therefore, read operations are much more efficient than write operations, and it takes a short time to use a read/write lock. The read/write lock is a non-static class, so you must declare a read/write Lock Object before using it:

Static private readerwriterlock _ rwlock = new
Readerwriterlock ();

Read/write locks are controlled by calling acquirereaderlock, releasereaderlock, acquirewriterlock, and releasewriterlock.

 

 

Static public void readerthread (INT thrdid) {try {// request read lock. If Ms times out, exit _ rwlock. acquirereaderlock (10); try {int round = _ Rand. next (_ list. count); If (distinct <_ list. count) console. writeline ("{0} thread {1}", thrdid, _ list [locked]);} finally {_ rwlock. releasereaderlock () ;}} catch (applicationexception) // if the request fails to read the lock {console. writeline ("{0} thread get Reader lock out time! ", Thrdid) ;}} static public void writerthread () {try {// request write lock _ rwlock. acquirewriterlock (100); try {string val = _ Rand. next (1, 200 ). tostring (); _ list. add (VAL); // write to the resource console. writeline ("writer thread has written {0}", Val) ;}finally {// release the write lock _ rwlock. releasewriterlock () ;}} catch (applicationexception) {console. writeline ("Get writer thread lock out time! ");}}

If you want to insert write data during reading, use upgradetowriterlock and downgradefromwriterlock instead of releasing the read lock.

 

Static private void upgradeanddowngrade (INT thrdid) {try {_ rwlock. acquirereaderlock (10); try {// upgrade the read lock to the write lock cookie lc = _ rwlock. upgradetowriterlock (100); try {string val = _ Rand. next (1, 500 ). tostring (); _ list. add (VAL); console. writeline ("upgrade thread {0} Add {1}", thrdid, Val);} finally {// drop write lock _ rwlock. downgradefromwriterlock (ref Lc) ;}} catch (applicationexception) {console. writeline ("{0} thread upgrade reader lock failed! ", Thrdid) ;}} finally {// release the original read lock _ rwlock. releasereaderlock () ;}} catch (applicationexception) {console. writeline ("{0} thread get Reader lock out time! ", Thrdid );}}

 

One thing to note here is the setting of the timeout interval between the read lock and the write lock. Generally, the lock wait time-out period is longer than that of the read lock. Otherwise, the write lock wait may often fail.

Mutex)

 

A mutex is similar to a monitor object. It ensures that a code block is executed by only one thread at a time. The main difference between a mutex object and a monitor object is that the mutex object is generally used for cross-process thread synchronization, while the monitor object is used for intra-process thread synchronization. Two types of mutex objects are available: one is name mutex, and the other is anonymous mutex. In a cross-process, naming mutex is used. A named mutex is a system-level mutex, which can be used by other processes, you only need to specify the name to enable the mutex when creating the mutex. In. net, mutex is implemented through the mutex class.

In fact, there are two overloaded versions of the openexisting function,

Mutex. openexisting (string)

Mutex. openexisting (string, mutexrights)

For the default first function, the second function is actually implemented.
Mutexrights. Synchronize | mutexrights. Modify operation.

Because the monitor is designed based on.. NET Framework, while the mutex class is a system kernel object that encapsulates a Win32 kernel structure to achieve mutual exclusion, and the mutex operation must be completed by request interruption, therefore, the performance of intra-process thread synchronization is better than mutex.

A typical mutex synchronization requires three steps: 1. open or create a mutex instance; 2. call waitone () to request mutex objects; 3. finally, call releasemutex to release the mutex object.

 

Static public void addstring (string Str) {// set the timeout period and exit the non-default hosted context if (_ CTX. waitone (1000, true) {_ resource. add (STR); _ CTX. releasemutex ();}}

 

It should be noted that waitone and releasemutex must appear in pairs; otherwise, the process will be deadlocked. In this case, the system (. net2.0) framework will throw an abandonedmutexexception.

 

Semaphores (semaphore)

 

A semaphore is like a nightclub: it has an exact capacity and is controlled by a guard. Once the staff is full, no one can enter again, and others must queue up outside. After leaving a person in the team, the person in the team can enter. The semaphore constructor must provide at least two parameters-the current number of people and the maximum number of people.

The semaphore behavior is similar to mutex or lock, but the semaphore has no owner. Any thread can call release to release semaphores. Unlike mutex and lock, the thread needs to obtain resources to release them.

 

Class semaphoretest {static semaphore S = new semaphore (3, 3); // current value = 3; capacity = 3 static void main () {for (INT I = 0; I <10; I ++) New thread (GO ). start ();} static void go () {While (true) {S. waitone (); thread. sleep (100); // only one thread can be processed at a time. release () ;}} event (manualresetevent/autoresetevent) <src = "http://blog.csdn.net/count.aspx? Id = 1857459 & type = rank "type =" text/JavaScript ">

Autoresetevent

 

 

An autoresetevent is like a "ticket check wheel": insert a pass and let a person pass. "Auto" means that this "Wheel" is automatically closed or opened to let someone pass. The thread will wait or block after waitone is called, and insert the thread by calling the set operation. If a bunch of threads call the waitone operation, a waiting queue will be created for "wheel disk. A pass can come from any thread. In other words, any thread can release a blocked thread by accessing the autoresetevent object and calling set.

 

If no thread waits when the set is called, the handle will remain open until a thread calls the waitone operation. This behavior avoids competition conditions-when a thread is not in urgent need of release and another thread starts to enter. Therefore, repeatedly calling set to operate a "wheel disk" won't let all threads enter at one time even if there is no waiting thread.

 

The waitone operation accepts a timeout parameter-when a wait timeout occurs, this method returns a false value. When a thread is waiting, the waitone operation can specify whether to wait or exit the current synchronization context. The reset operation allows you to disable "wheel disk. Autoresetevent can be created in two ways:
1. Call the constructor eventwaithandle wh = new autoresetevent (false );
If the Boolean value is true, the set operation of the handle will be called automatically after creation; 2. eventwaithandle method using the base class eventwaithandle
Wh = new eventwaithandle (false, eventresetmode. Auto );
The eventwaithandle constructor allows you to create a manualresetevent. People should call close to release a wait
Handle is no longer used. When wait handle continues to be used during the lifetime of the application, if the close step is omitted, it will be automatically released when the application is closed.

 

Class basicwaithandle {static eventwaithandle wh = new autoresetevent (false); static void main () {New thread (waiter ). start (); thread. sleep (1000); // wait for a moment wh. set (); // wake up} static void waiter () {console. writeline ("waiting... "); Wh. waitone (); // wait for the console to wake up. writeline ("notified ");}}

Manualresetevent

Manualresetevent is a special case of autoresetevent. It does not automatically reset the status after the thread calls waitone. Its working mechanism is a bit like a switch: Call set to open and allow other threads to waitone; call reset to close the waiting thread until the next open. You can use a Boolean field with volatile declaration to simulate intermittent sleep.
-Repeat the detection mark and then sleep for a short period of time.

Manualresetevent is often used to assist with a special operation, or let a thread complete initialization before it starts working.

 

Thread pooling)

 

If your application has a large number of threads and spends a lot of time blocking in a wait
In handle, you need to consider using thead pooling for processing. The thread pool combines multiple wait handle to save the waiting time. When wait
When handle is activated, You need to register a wait instance to use the thread pool.
Handle is executed by a delegate. Call the threadpool. registerwaitforsingleobject method:

 

class Test     {        static ManualResetEvent starter = new ManualResetEvent(false);         public static void Main()        {            ThreadPool.RegisterWaitForSingleObject(starter, Go, "hello", -1, true);            Thread.Sleep(5000);            Console.WriteLine("Signaling worker...");            starter.Set();             Console.ReadLine();        }        public static void Go(object data, bool timedOut)         {            Console.WriteLine("Started " + data); // Perform task...         }    }

 

For wait
Handle and delegate. registerwaitforsingleobject accepts a "black box" object and passes it to your delegate (like parameterizedthreadstart). The timeout setting and Boolean flag indicate the requests to close and loop. All threads entering the pool are considered as backend threads, which means they are not controlled by the application, but by the system until the application exits.

 

Note: If you call the abort operation at this time, unexpected situations may occur.

 

You can also use the thread pool by calling the queueuserworkitem method, specify the delegate and be executed immediately. At this time, you cannot save the shared thread under multi-task conditions, but you can get another benefit: the thread pool will maintain the total capacity of a thread and automatically insert tasks when the number of jobs exceeds the capacity.

 

 

class Test     {        static object workerLocker = new object();        static int runningWorkers = 100;        public static void Main()         {            for (int i = 0; i < runningWorkers; i++)             {                ThreadPool.QueueUserWorkItem(Go, i);             }            Console.WriteLine("Waiting for threads to complete...");             lock (workerLocker)             {                while (runningWorkers > 0)                     Monitor.Wait(workerLocker);            }            Console.WriteLine("Complete!");            Console.ReadLine();         }        public static void Go(object instance)         {            Console.WriteLine("Started: " + instance);            Thread.Sleep(1000);             Console.WriteLine("Ended: " + instance);             lock (workerLocker)            {                runningWorkers--;                Monitor.Pulse(workerLocker);            }        }    }

To pass multiple objects to the target method, you must define a customer object and contain all attributes or call asynchronous delegation. For example, the Go method accepts two parameters:

Threadpool. queueuserworkitem (delegate (Object
Notused) {go (23,34 );});

Other methods can use Asynchronous delegation.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.