Data synchronization is required when you need 2 threads to read and write the same data. The methods of thread synchronization are: (1) atomic operation; (2) lock. Atomic operations ensure that the operation is not "split" in the CPU core, the lock guarantees that only one thread accesses the data, and other threads are rejected when attempting to acquire the locked data, until the thread that is currently getting the data is released, and the other line friend can get the data.
- Why thread synchronization?
Let's look at an example that requires data synchronization,
static void Main (string [] args) { bool flag = false ; var t1 = new Thread (() = {if (flag ) Console.WriteLine ( " flag " ); }); var t2 = new Thread (() = {flag = true ;});
T1. Start ();
T2. Start (); Console.ReadLine ();}
In the example above, the T2 thread would set the flag to true, which could happen: when T2 intends to execute flag = true, T1 executes an if (flag) statement, which creates an unknowable situation. At this point, when T2 execution, if T1 want to get the value of flag, wait until the flag=true execution completes, and then execute, this is called "Thread Synchronization", one thread waits for another thread to execute to a piece of code, then executes. Thread synchronization ensures that the execution of the program conforms to the "expected"-if T2 is not executed, the flag is false,t2 if executed, then flag=true. Thread synchronization is to prevent T2 from executing flag=true when T1 starts execution, at which point flag should be true because T2 has started to execute, but actually flag=false, because T2 's flag=true is not done. The solution is to block any thread that attempts to read flag when T2 executes Flag=true, until the flag=true execution finishes and the other thread executes. Code similar to the following.
varM_lock =Getsomelock ();
Pulick void Go () {
var T1 = new Thread (() =>go1 ());
var t2 = new Thread (() =>go2 ());
T1. Star ();
T2. Start ();
} Public voidGo1 () {m_lock.Lock(); if(flag)//dosomething; Console.WriteLine (flag);
M_lock. Unlock ();} Public voidGo2 () {m_lock.Lock(); Flag=true; M_lock. Unlock ();}
Add M_lock.lock () and m_lock outside the flag=true and if flag. Unlock () is to ensure thread synchronization. But the problem with this synchronization is performance degradation and the possibility of deadlocks. As explained in the summary, there are 2 methods for thread synchronization, the above describes the lock, and the atomic operation I did not introduce. Before I introduce atomic operations, I introduce the following keyword volatile.
The keyword can act before a variable, meaning that the read and write operation of the variable is atomic, which is called "variability".
During the compilation process, the compiler will "optimize" according to the specifics of the code, for example:
Public void Go () { int12; for (int0; i < value; i++) Console.WriteLine (i);}
The compiler will skip the statement when it sees a place to call the method, because this statement is meaningless, which of course is good and the compiler makes up for our mistakes. But sometimes this optimization can result in the effects we don't want.
Private Static BOOLS_stopworker =false;Static voidMain (string[] args) {Console.WriteLine ("main:letting worker run for 5s"); vart =NewThread (Worker); T.start (); Thread.Sleep ( the); S_stopworker=true; Console.WriteLine ("main:waiting for worker to stop."); T.join ();}Private Static voidWorker (Objecto) { intx =0; while(s_stopworker) x + +; Console.WriteLine ("worker:stopped when x = {0}", x);}
In this code, the main thread blocks for 5 seconds and then S_stopworker=true, which is meant to break the T thread and let it show up after the number of digits returned. But in fact, when the compiler sees while (S_stopworker), and see that S_stopworker does not change in the worker method, the judgment of S_stopworker in this method will only be judged at the very beginning, if s_stopworker= True to enter the dead loop and, if false, the worker stopped when x = 0 is returned. To actually see the effect, you need to put the shortened code in the. cs file and compile the code with the command line. Compile code from the command line to add an environment variable, the path to the variable is C:\Windows\Microsoft.NET\Framework\v4.0.30319. You can then compile the file on the command line, and note that to open/platform:x86, the meaning is explained in the CLR via C # 29, the x86 compiler is more mature than the x64 compiler and more daring to optimize. Enter the path to the Csc/platform:x86 your CS file on the command line, and after the input Program.exe (assuming your file name is Program.cs), then you will see that the program has been stuck in the main:waiting for worker to Stop. No number has been seen since then.
Here's how to solve this problem. 2 static methods are provided in the System.Threading.Volatile,
Public Static class volatile{ publicstaticbool Read (refbool location ) ; Public Static BOOL Write (refbool location, bool value);}
These two methods can prevent the compiler from optimizing read and write, and the modified code is as follows:
Private Static BOOLS_stopworker =false;Static voidMain (string[] args) {Console.WriteLine ("main:letting worker run for 5s"); vart =NewThread (Worker); T.start (); Thread.Sleep ( the); //Prevent optimizationVolatile.write (refS_stopworker,true); Console.WriteLine ("main:waiting for worker to stop."); T.join (); Console.read ();}Private Static voidWorker (Objecto) { intx =0; //Prevent optimization while(Volatile.read (refS_stopworker)) x + +; Console.WriteLine ("worker:stopped when x = {0}", x);}
The Read and write methods in the volatile class were used in the Read and write section of the S_stopworker. Compiling the code again using the command line will find that it is working correctly. Most of the time we can't figure out when to call the read and write in volatile, and when to read and write normally, C # provides the volatile keyword, which guarantees that the read and write of the variable is atomic and prevents the optimization of the method. In order to improve the efficiency of the CPU, the current program is in order to execute, but volatile to ensure that the keyword before the code will be executed when the keyword read and write, the keyword-modified variable code will be executed later, and will not be executed in order to optimize the chaos. We remove volatile.write and read, and then add the volatile keyword to the s_stopworker before running the above code and find the result is correct.
The volatile keyword guarantees the thread safety of the variable, but its disadvantage is also obvious, and it is a waste of performance to change each read and write of a variable to read and write, which is a very rare occurrence.
volatile int 5 ; M=m+m; // volatile will prevent optimization
In general, it is possible to increase one variable by one time, just by moving the variable left one bit, but volatile blocks the optimization. The CPU reads M into a register and then reads into another register, then executes add and writes the result to M. If M is not an int type, but a larger type, it will cause a greater waste, if in the loop, that is really a cup.
In addition, C # does not support the use of volatile modified variables in the form of a reference to the method, such as Int32.TryParse ("123", m); a warning will be given that a reference to the volatile field will not be considered volatile.
- Variable Capture (closure)
In the first piece of code, the flag variable is contained by the LAMDA expression. The program is not executed in the main thread, but is executed in T1 and T2, which is out of its scope, in order to ensure that the flag variable can take effect, the compiler is responsible for extending the flag's life cycle to ensure that the variable can be accessed when the T1 and T2 threads execute, which is variable capture, also called "Closure", You can use the Il anti-compiler to view the IL directives for the above code to verify.
You can see that in order to ensure that the life cycle compiler of the flag uses a class of 2 Lamda expressions (B_0 and b_1) and flag, the 3 life cycles are consistent. That's good, because we don't need to care if flag is valid when T1 and T2 get the flag value, the compiler has done it for us all.
This article discusses the need for thread safety and one of the means of threading security: volatile (variability), and a simple introduction to variable capture. Thread-safe content is not finished, it is expected to be 3-4 blog posts thread-safe. Welcome the small partner in the comment area to communicate with me.
C # multithreaded programming (5)--Thread safety 1