Reference: https://getpocket.com/a/read/929454653
first, look at a race condition
The Sharedstate class is used to hold congratulations on the data between threads, there is a Member State, and different threads can share state.
public class Sharedstate {public int state {get; set;} }
The job class contains the Dothejob () method, which is the entry point for the newly task, implemented by code, incrementing the state of the Sharedstate class by 50,000 times, sharedstate initialized in the constructor of the class (This.sharedstate)
public class Job { sharedstate sharedstate; Public Job (sharedstate sharedstate) { this.sharedstate = sharedstate; } Private Object syncobj = new Object (); public void Dothejob () {for (int i = 0; i < 50000; i++) {sharedstate.state + = 1; } } }
In the main method, create a Sharedstate object and pass it to the constructor of the 20 task object, after starting all the tasks, the Main () method enters another loop, waits for 20 tasks to complete, writes the shared state value to the console, Because 50,000 loops were performed, there were 20 tasks, so the expected result was 1 000 000, but that was not the case.
Class program { static void Main (string[] args) { int numtasks =; var state = new Sharedstate (); var tasks = new Task[numtasks]; for (int i = 0; i < Numtasks, i++) { Tasks[i] = Task.run (() = new Job (state). Dothejob ()); } for (int i = 0; i < numtasks; i++) { tasks[i]. Wait (); } Console.WriteLine ("Summarized {0}", state. State); Console.readkey (); } }
The result of the execution three times is:
Summarized 381758
Summarized 531860
Summarized 316794
Different machine execution results may be different, but to illustrate a problem, in the context of multi-threaded parallel execution, congratulations that the data may be modified by other threads, resulting in unintended results.
For example, a total of two threads T1 and T2, the two threads take the value of the state and give it 1,state initial values of 1, when the T1 and T2 from the state to take out the value of 1, the respective execution plus 1 operation will return the result, then we get the result is 2, not 3, Because the state value that one thread takes out is not the result of another thread's execution, it causes a result error.
Second, C # Technology for multiple thread synchronization
If you need to share data in a thread, you need to use synchronization technology, the techniques that C # can use for multithreaded synchronization are:
- Lock statement
- Interlocked class
- Monitor class
- SpinLock structure
- WaitHandle class
- Mutex class
- Semaphore class
- Event class
- Barrier class
- ReaderWriterLockSlim
1. Lock Statement
The object representation defined by the lock statement, which is to wait for the specified object's lock, to pass only the reference type. Locking the type only locks a copy, which makes little sense, and the C # compiler issues an error if the lock statement is used on the value type. After locking--only one thread is locked, the lock statement block can be run, and at the end of the lock statement block, the lock is unlocked and another thread waiting to be locked can get the lock block.
We try to use lock (this) and lock (obj) to lock.
The following modifications are made to the Dothejob:
public void Dothejob () { lock (this) {for (int i = 0; i < 50000; i++) { sharedstate.state + = 1; } }
The result is still not up to our expected 100 000, where the lock detachment works with threads of the same instance, tasks
Each character in [] invokes a different instance, so they all can use the Dothejob method at the same time.
The following modifications are made to the Dothejob:
Private Object syncobj = new Object (); public void Dothejob () { lock (syncobj) {for (int i = 0; i < 50000; i++) { Sharedstate.state + = 1;}}
The result of the operation is also incorrect. Lock (Syncobj) only causes dothejob () to be inaccessible to other threads, but other members of the instance can still be accessed.
This can be explained more clearly in the following example.
Lock (This)
public class LockThis { private bool DeadLock = true; public void deadlocked () { lock (this) {while (DeadLock) { Console.WriteLine ("Omg! I am locked! "); Thread.Sleep (+); } Console.WriteLine ("Deadlocked () End"); } } public void Dontlockme () { deadLock = false; } Public static void Lockthismethod () { LockThis LockThis = new LockThis (); Task.Factory.StartNew (lockthis.deadlocked); Thread.Sleep (the); Lock (LockThis) { lockthis.dontlockme ();}} }
This method can be called by calling Lockthis.lockthismethod in Main ().
Operation Result:
In the Lockthismethod method, start the task Lockthis.deadlocke
Task.Factory.StartNew (lockthis.deadlocked);
An attempt was made to unlock the deadlock by Lockthis.dontlockme after the task started 5s, but it did not succeed and lockthis.deadlocked was not stopped.
Even the non-synchronous method Dontlockme () cannot be invoked because lock (this) locks the entire instance in a deadlock, causing the outer layer to synchronize access to this instance.
Lock (Syncobj)
public class LockObject { private bool DeadLock = true; Private Object syncobj = new Object (); public void deadlocked () { lock (syncobj) {while (DeadLock) { Console.WriteLine ("Omg! I am locked! "); Thread.Sleep (+); } Console.WriteLine ("Deadlocked () End."); } } public void Dontlockme () { deadLock = false; } public static void Lockobjectmethod () { LockObject lockobject = new LockObject (); Task.Factory.StartNew (lockobject.deadlocked); Thread.Sleep (the); Lock (LockObject) { lockobject.dontlockme ();}} }
This method can be called by calling Lockobject.lockobjectmethod in Main ().
Operation Result:
In the Lockobjectmethod method, start the task Lockobject.deadlocke
Task.Factory.StartNew (lockobject.deadlocked);
The attempt task began 5s later through Lockthis.dontlockme to unlock the deadlock, succeeded
Because lock (syncobj) locks only the deadlocked () method in deadlock, the non-synchronous method Dontlockme can be called when the outer layer also accesses the instance synchronously.
Summary: Because objects of a class can also be used for external synchronous access (the above lock (LockThis) and lock (lockobject) simulate such access), and we cannot control such access in the class itself, we should use Lock (obj) as much as possible, You can have more precise control over the range that needs to be synchronized.
Lock (this) locks the entire instance
Lock (obj) locks only the code within the range.
Lock (Typeod (staticclass)) locks static members
To take a look at it, modifying the Sharedstate class is infeasible.
public class Sharedstate { Private object syncobj = new Object (); private int state=0; public int State { get {lock (syncobj) {return _state;}} Set {lock (syncobj) {state = value;}}}}
The shared state control is synchronized directly, but the expected result is still not coming out.
Misunderstanding: The understanding of synchronization is wrong, between reading and writing, Syncobj is not locked, still wired can be obtained during this period value.
Solution to the problem:
1. Put the lock in the right place and use the appropriate lock object
public void Dothejob () {for (int i = 0; i < 50000; i++) { lock (sharedstate) { Sharedstate.state + = 1;}}
2, modify the design of the Sharedstate class, as an atomic operation to provide an incremental way
public class Sharedstate { private int state = 0; Private Object syncRoot = new Object (); public int State { get {return ' state;} } public int incrementstate () { lock (syncRoot) { return ++state; }}}
Thread Synchronization Technology