For more information about how to implement multi-threaded data sharing in. net, see. net.
Data sharing between threads may result in a race rate and data inconsistency. For example:
The Code is as follows: |
Copy code |
Namespace TaskParallel { Class Account { Public int Balance { Get; Set; } } Class Share { Static void Main (string [] args) { Account account = new Account {Balance = 0 }; List <Task> tasks = new List <Task> (); For (int I = 0; I <10; I ++) { Tasks. Add (Task. Factory. StartNew () => { For (int j = 0; j <1000; j ++) Account. Balance ++; })); } Task. WaitAll (tasks. ToArray ()); Console. WriteLine (account. Balance ); } } } |
In this program, there are a total of 10 threads, each thread automatically adds an integer variable 1000 times, the expected result should eventually be 10000, however, the results of running this program are different each time and the total result is smaller than 10000. The reason is that the auto-increment of a variable is not an atomic operation. If you ignore the specific machine code, you should take three steps to read the current value, add 1, and save it back to the calculated value. if thread 1 reads the current value 0, it is replaced by thread 2 and enters the waiting state. Thread 2 reads the current value 0, adds 1, and stores 1 back, thread 1 then runs, add 1, and save 1. In this case, the Balance value is 1, and thread 1 and thread 2 have been added twice. data inconsistency occurs. The following describes the thread mutex method provided by. Net. Its implementation principles are described in detail in the operating system principles.
1. Use Monitor
To avoid inconsistency, ensure that only one thread is executing the code that can change the shared data at the same time. To achieve this, you can use the lock keyword of C:
The Code is as follows: |
Copy code |
Object obj = new object (); for (int I = 0; I <10; I ++) { Tasks. Add (Task. Factory. StartNew () => { For (int j = 0; j <1000; j ++) { Lock (obj) { Account. Balance ++; } } })); } |
Lock is actually a packaging of the Monitor class. To use more complete functions, you can use the Monitor class. 2. Use Spin Locking.
The implementation of Spin Locking is similar to that of Monitor, but the principle is different. Spin Locking does not block the current thread, but uses a loop to constantly determine whether the access conditions are met. When the expected blocking time is not too long, it is more efficient than the Monitor class, but not suitable for cases that require long blocking.
The Code is as follows: |
Copy code |
SpinLock locker = new SpinLock (); For (int I = 0; I <10; I ++) {Tasks. Add (Task. Factory. StartNew () => { For (int j = 0; j <1000; j ++) { Bool lockAcquired = false; Try { Locker. Enter (ref lockAcquired ); Account. Balance ++; } Finally { Locker. Exit (); } } })); } |
3. Use Mutex and Semaphore
Mutex and Semaphore all inherit from the WaitHandle class and can implement thread Mutex. WaitHandle is the packaging of synchronization handles in windows.
First, we will introduce the Mutex example:
The Code is as follows: |
Copy code |
Mutex mutex = new Mutex (); For (int I = 0; I <10; I ++) { Tasks. Add (Task. Factory. StartNew () => { For (int j = 0; j <1000; j ++) { Bool lockAcquired = mutex. WaitOne (); Account. Balance ++; if (lockAcquired) Mutex. ReleaseMutex (); } })); } |
WaitHandle's WaitAll method can obtain multiple locks at the same time. For example, in the following program, there are two accounts that need two locks to keep them only accessible by one thread at a time. The third thread Accesses both accounts at the same time. Therefore, you need to obtain the locks for the two accounts at the same time. When the third thread ends the access, remember to release the two locks.
The Code is as follows: |
Copy code |
Using System; Using System. Collections. Generic; Using System. Linq; Using System. Text; Using System. Threading; Using System. Threading. Tasks; Namespace TaskParallel { Class Account { Public int Balance { Get; Set; } } Class Share { Static void Main (string [] args) { Account account1 = new Account {Balance = 0 }; Account account2 = new Account {Balance = 0 }; Mutex mutex1 = new Mutex (); Mutex mutex2 = new Mutex (); Task t1 = new Task () => { For (int I = 0; I <1000; I ++) { Bool locked = mutex1.WaitOne (); Account1.Balance ++; If (locked) Mutex1.ReleaseMutex (); } }); Task t2 = new Task () => { For (int I = 0; I <1000; I ++) { Bool locked = mutex2.WaitOne (); Account2.Balance ++; If (locked) Mutex2.ReleaseMutex (); } }); Task t3 = new Task () => { For (int I = 0; I <1000; I ++) { Bool locked = WaitHandle. WaitAll (new WaitHandle [] {mutex1, mutex2 }); Account1.Balance --; Account2.Balance --; If (locked) // release two locks { Mutex1.ReleaseMutex (); Mutex2.ReleaseMutex (); } } }); T1.Start (); T2.Start (); T3.Start (); Task. WaitAll (t1, t2, t3 ); Console. Write ("Balance1: {0}, Balance2: {1}", account1.Balance, account2.Balance ); Console. ReadLine (); } } } |
1 2