Home > Article > Backend Development > Detailed introduction to thread synchronization code in C#
This article mainly introduces the relevant knowledge of thread synchronization in C#. It has a very good reference value. Let’s take a look at it with the editor.
Preface
When a thread in the thread pool is blocked, the thread pool will create additional threads , and creating, destroying and scheduling threads requires quite expensive memory resources. In addition, many developers are used to creating more threads when they see that the threads of their programs are not doing anything useful. In order to build scalable and responsive programs , We introduced the detailed explanation of C# asynchronous programming before
But asynchronous programming also has serious problems. If two different threads access the same variables and data, according to the implementation of our asynchronous function, no There may be two threads accessing the same data at the same time. In this case, we need thread synchronization. When multiple threads access shared data at the same time, thread synchronization can prevent data corruption. The reason why the concept of simultaneous is emphasized is that the essence of thread synchronization is a timing issue.
Asynchronous and synchronous are relative. Synchronization means sequential execution. After executing one, you need to wait and coordinate the execution of the next one. Asynchronous means that they are independent of each other and continue to do their own things while waiting for an event. There is no need to wait for the event to complete before working again. Threads are a way to achieve asynchronous. Asynchronous means that the main thread that calls the method does not need to wait for the completion of another thread synchronously, so that the main thread can do other things.
Primitive user mode and kernel mode constructs
Basic concepts
Primitive: Yes Simple constructs used in code
User-mode: By coordinating threads through special CPU instructions, the operating system never detects that a thread is blocked on a primitive user-mode construct.
Kernel mode: Provided by windows itself, functions implemented by the kernel are called in the thread of the application.
User Mode Constructs
Volatile Constructs
C# Compiler, JIT Compiler and CPUs will optimize the code, and they try to ensure that our intentions are retained. However, from a multi-threading perspective, our intentions may not be retained. Here is an example:
static void Main(string[] args) { Console.WriteLine("让worker函数运行5s后停止"); var t = new Thread(Worker); t.Start(); Thread.Sleep(5000); stop = true; Console.ReadLine(); } private static bool stop = false; private static void Worker(object obj) { int x = 0; while (!stop) { x++; } Console.WriteLine("worker函数停止x={0}",x); }
If the compiler checks that stop is false, it generates code to enter an infinite loop and keeps incrementing x in the loop, so the loop is optimized It is completed quickly, but the compiler only detects stop once, not every time.
Example 2---Two threads access simultaneously:
class test { private static int m_flag = 0; private static int m_value = 0; public static void Thread1(object obj) { m_value = 5; m_flag = 1; } public static void Thread2(object obj) { if (m_flag == 1) Console.WriteLine("m_value = {0}", m_value); } //多核CPU机器才会出现线程同步问题 public void Exec() { var thread1 = new Thread(Thread1); var thread2 = new Thread(Thread2); thread1.Start(); thread2.Start(); Console.ReadLine(); } }
When the program is executed, the compiler must change the variables m_flag and m_value Read the CPU register from RAM. RAM first transfers the value of m_value 0, and thread1 changes the value to 5. However, thread2 does not know that thread2 still thinks the value is 0. Generally speaking, this problem is more likely to occur in multi-core CPUs. It should The more CPUs there are, the greater the chance that multiple threads will access resources at the same time.
The keyword volatile is used to disable some optimizations performed by the C# compiler, JTP compiler and CPU. If applied to variables, the field will not be allowed to be cached in the CPU register, ensuring that the reading and writing of the field are safe. Do it in RAM.
Interlocking structure
Each method in the System.Threading.Interlocked class performs an atomic read and write operation before calling an Interlocked method Any variable writes are performed before this call to the Interlocked method, and any variable reads after the call are performed after this call.
The Interlocked method mainly performs static operations on INT32 variables such as Add, Decrement, Compare, Exchange, CompareChange and other methods, and also accepts parameters of object, Double and other types.
Atomic operation: refers to an operation that will not be interrupted by the thread scheduling mechanism; once this operation starts, it will run until the end without any context switch (switch to another thread) in the middle.
Code demonstration:
Description: Asynchronously query several web servers through the Interlocked method and return data at the same time, and the results are only executed once.
//上报状态类型 enum CoordinationStatus { Cancel, Timeout, AllDone }
class AsyncCoordinator { //AllBegun 内部调用JustEnded来递减它 private int _mOpCount = 1; //0=false,1=true private int _mStatusReported = 0; private Action<CoordinationStatus> _mCallback; private Timer _mTimer; //发起一个操作之前调用 public void AboutToBegin(int opsToAdd = 1) { Interlocked.Add(ref _mOpCount, opsToAdd); } //处理好一个操作的结果之后调用 public void JustEnded() { if (Interlocked.Decrement(ref _mOpCount) == 0) { ReportStatus(CoordinationStatus.AllDone); } } //该方法必须在发起所有操作后调用 public void AllBegin(Action<CoordinationStatus> callback, int timeout = Timeout.Infinite) { _mCallback = callback; if (timeout != Timeout.Infinite) { _mTimer = new Timer(TimeExpired, null, timeout, Timeout.Infinite); JustEnded(); } } private void TimeExpired(object o) { ReportStatus(CoordinationStatus.Timeout); } public void Cancel() { ReportStatus(CoordinationStatus.Cancel); } private void ReportStatus(CoordinationStatus status) { //如果状态从未报告过,就报告它,否则就忽略它,只调用一次 if (Interlocked.Exchange(ref _mStatusReported, 1) == 0) { _mCallback(status); } } }
class MultiWebRequest { //辅助类 用于协调所有的异步操作 private AsyncCoordinator _mac = new AsyncCoordinator(); protected Dictionary<string,object> _mServers = new Dictionary<string, object> { {"http://www.baidu.com",null},{"http://www.Microsoft.com",null},{"http://www.cctv.com",null}, {"http://www.souhu.com",null},{"http://www.sina.com",null},{"http://www.tencent.com",null}, {"http://www.youku.com",null} }; private Stopwatch sp; public MultiWebRequest(int timeout = Timeout.Infinite) { sp = new Stopwatch(); sp.Start(); //通过异步方式一次性发起请求 var httpclient = new HttpClient(); foreach (var server in _mServers.Keys) { _mac.AboutToBegin(1); httpclient.GetByteArrayAsync(server).ContinueWith(task => ComputeResult(server, task)); } _mac.AllBegin(AllDone,timeout); Console.WriteLine(""); } private void ComputeResult(string server, Task<Byte[]> task) { object result; if (task.Exception != null) { result = task.Exception.InnerException; } else { //线程池处理IO result = task.Result.Length; } //保存返回结果的长度 _mServers[server] = result; _mac.JustEnded(); } public void Cancel() { _mac.Cancel(); } private void AllDone(CoordinationStatus status) { sp.Stop(); Console.WriteLine("响应耗时总计{0}",sp.Elapsed); switch (status) { case CoordinationStatus.Cancel: Console.WriteLine("操作取消"); break; case CoordinationStatus.AllDone: Console.WriteLine("操作完成,完成的结果如下"); foreach (var server in _mServers) { Console.WriteLine("{0}",server.Key); object result = server.Value; if (result is Exception) { Console.WriteLine("错误原因{0}",result.GetType().Name); } else { Console.WriteLine("返回字节数为:{0}",result); } } break; case CoordinationStatus.Timeout: Console.WriteLine("操作超时"); break; default: throw new ArgumentOutOfRangeException("status", status, null); } } }
It is highly recommended that you refer to it In the above code, I often refer to this model when accessing the server.
Simple spin lock
class SomeResource { private SimpleSpinLock s1 = new SimpleSpinLock(); public void AccessResource() { s1.Enter(); //一次是有一个线程才能进入访问 s1.Leave(); } } class SimpleSpinLock { private int _mResourceInUse; public void Enter() { while (true) { if(Interlocked.Exchange(ref _mResourceInUse,1)==0) return; } } public void Leave() { Volatile.Write(ref _mResourceInUse,1); } }
This is a simple implementation of a thread synchronization lock. This lock The biggest problem is that the presence of competition will cause the thread to "spin", which will waste precious time of the CPU and prevent the CPU from doing more work. Therefore, this spin lock should be used to protect those that execute very quickly. code.
The above is the detailed introduction of thread synchronization code in C#. For more related content, please pay attention to the PHP Chinese website (www.php.cn)!