Multithreading programming learning notes-thread synchronization (1), multithreading programming learning notes
Link to multi-thread programming learning notes-basics (1) link to multi-thread programming learning notes-basics (2) link to multi-thread programming learning notes-basics (3)
Just like the sample code 10 in the previous article (multithreading programming study note-basics (III), if multithreading uses SHARED variables, it will involve a thread synchronization problem. How can this problem be solved?
There are three methods:
1) refactor the program, remove multi-threaded shared variables, and allow a thread to access only one self-owned variable
2) with atomic operations, one operation takes only one quantum time and is completed at a time. Other threads can operate only after the current operation is completed. This prevents exclusive locks and deadlocks.
3) use the Mutex, AutoRestEvent, CountDownEven, and SpinWait classes provided by the NET architecture to synchronize threads.
I. Use the InterLocked class
In the previous article, we used lock to solve the problem caused by multi-threaded access. Here we use the atomic operation provided by the InterLocked class, so that we do not need to lock the lock, to avoid deadlock.
Next, let's transform the code in the previous article. The Code is as follows.
Using System; using System. collections. generic; using System. linq; using System. text; using System. threading; // introduce the thread using System. diagnostics; namespace ThreadSynchronousDemo {class Program {static void Main (string [] args) {Console. writeLine ("START, InterLocked synchronization"); var c = new Counter (); Thread t = new Thread () => Count (c )); var t3 = new Thread () => Count (c); var t2 = new Thread () => Count (c); t. name = "thread 1"; // start thread t. start (); t2.Name = "thread 2"; t2.Start (); t3.Name = "thread 3"; t3.Start (); t. join (); t2.Join (); t3.Join (); Console. writeLine (string. format ("Total threads without locks: {0}", c. count); Console. writeLine ("----------------------------"); var c1 = new CounterInterLocked (); var t4 = new Thread () => Count (c1); t4.Name = "Thread 4 "; var t5 = new Thread () => Count (c1); t5.Name = "Thread 5"; var t6 = new Thread () => Count (c1 )); t6.Name = "thread 6"; t4.Start (); t5.Start (); t6.Start (); t4.Join (); t5.Join (); t6.Join (); Console. writeLine (string. format ("InterLocked multithreading Total: {0}", c1.Count); Console. read ();} static void Count (CountBase cnt) {for (int I = 0; I <100000; I ++) {cnt. incerement (); cnt. dncerement () ;}} abstract class CountBase {public abstract void Incerement (); public abstract void Dncerement ();} class Counter: CountBase {public int Count {get; private set;} public override void Dncerement () {Count --;} public override void Incerement () {Count ++ ;}} class CounterInterLocked: CountBase {private int m_count; public int Count {get {return m_count;} public override void Dncerement () {Interlocked. decrement (ref m_count);} public override void Incerement () {Interlocked. increment (ref m_count );}}}
The program running result is the same as Example 10 in the previous article. Only the difference in code.
Ii. Use the Mutex class
1. Next we will learn how to use the Mutex class to implement synchronization between threads.
2. When the program starts, SetInitialOwnerFalseThis indicates that if the mutex has been created, the program is allowed to obtain the mutex. If no mutex exists, the program runs directly and waits for receiving any key.And then release the mutex.
3. The Code is as follows:
Using System; using System. collections. generic; using System. linq; using System. text; using System. threading; // introduce the thread using System. diagnostics; namespace ThreadSynchronousDemo {class Program {const string mutexName = "syncMutex"; static void Main (string [] args) {Console. writeLine ("START, Mutex synchronization"); using (var mut = new Mutex (false, mutexName) {if (! Mut. WaitOne (TimeSpan. FromSeconds (5), false) {Console. WriteLine ("wait for 5 seconds to run .... ");} Else {Console. writeLine ("running ...., enter any key "); Console. readLine (); mut. releaseMutex (); Console. writeLine ("release mutex") ;}} Console. read ();}}}
4. The running result is shown in.
In the debug directory, run the main program first. For example, if "medium 1" is used, program 1 runs normally. If you open the application main program in the Debug directory again, the running result is "medium 2. It indicates that the mutex amount takes effect.
5. Enter k in main program 1 and press Enter. The result is as follows: 3. Open the application again in the Debug directory, and the running result of the application is as follows: 4. It indicates that the mutex has been released in main program 1.
Note: The specified mutex is a global operating system object. Disable mutex correctly. It is best to use using to wrap mutex code. This method can be used to synchronize threads in different programs.
Iii. Use the SemaphoreSlim class
SemaphoreSlim is a lightweight version of the Semaphore class. This type limits the number of threads that access the same resource at the same time.
In. net, Semaphore encapsulates the kernel synchronization objects in CLR. Unlike the standard exclusive lock object (Monitor, Mutex, and SpinLock), it is not an exclusive lock object. It is different from SemaphoreSlim, readerWriteLock allows multiple limited threads to access shared memory resources at the same time.
Semaphore is like a fence with a certain capacity. When the number of threads in it reaches the set maximum value, no threads can enter. Then, if a thread comes out after the work is completed, the next thread can go in. The WaitOne or Release operations of Semaphore will automatically decrease or increase the current count of semaphores. When a thread tries to perform the WaitOne operation on a semaphore whose Count value is already 0, the thread will be blocked until the Count value is greater than 0. When constructing Semaphore, at least two parameters are required. The initial capacity and maximum capacity of the semaphore.
1. program code
Using System; using System. collections. generic; using System. linq; using System. text; using System. threading; // introduce the thread using System. diagnostics; namespace ThreadSynchronousDemo {class Program {static SemaphoreSlim semapSlim = new SemaphoreSlim (5); static void Main (string [] args) {Console. writeLine ("START, SemaphoreSlim synchronization"); for (int I = 1; I <9; I ++) {string threadName = "Thread" + I; int seconds = new Random (). next (1, 10); var t = new Thread () => AccessDatabase (threadName, seconds); t. start ();} Console. read ();} static void AccessDatabase (string name, int seconds) {Console. writeLine ("{0} waiting for database access", name); semapSlim. wait (); Console. writeLine ("{0} authorized to access the database", name); Thread. sleep (TimeSpan. fromSeconds (seconds); Console. writeLine ("{0} completed", name); semapSlim. release ();}}}
2. The program running result is shown in.
When the program starts, A SemaphoreSlim object is created, the number of concurrent threads is specified in the constructor, and 10 threads with different names and different initial runtime are started.
Every thread tries to obtain the database access permission, but we use the SemaphoreSlim object for restrictions. Only five threads can access the database at the same time. After the current five threads obtain the database access permission, the remaining five threads can only wait until a thread completes the work and calls the Release method of Semaphore.