C # multithreaded Programming series (iii)-thread synchronization

Source: Internet
Author: User
Tags mutex readline semaphore

Directory

    • 1.1 Introduction
    • 1.2 Performing basic atomic operations
    • 1.3 Using the Mutex class
    • 1.4 Using the Semaphoreslim class
    • 1.5 Using the AutoResetEvent class
    • 1.6 Using the ManualResetEventSlim class
    • 1.7 Using the Countdownevent class
    • 1.8 Using the Barrier class
    • 1.9 Using the ReaderWriterLockSlim class
    • 1.10 Using the SpinWait class
    • Reference books
    • The author level is limited, if the mistake welcome everybody criticism correct!
1.1 Introduction

This chapter describes several ways to implement thread synchronization in C #. Because multiple threads access shared data at the same time, it can cause corruption of shared data, which results in a non-compliance with the expected result. In order to solve this problem, we need to use thread synchronization, also known as "lock". But locking absolutely not improve performance, at most that is no increase , to achieve performance is not increased by the high quality of the synchronization source language (synchronization Primitive). But because correctness is always more important than speed , thread synchronization is necessary in some scenarios.

There are two provenances (Primitive) constructs for thread synchronization: user mode (User-mode) and kernel mode (kernel-mode), in which case the user mode is better than kernel mode when the resource is available for a short time. But if resources are not available for long periods of time, or if they are "spinning" for long periods of time, then kernel mode is a relatively good choice.

But we want to combine the advantages of user mode and kernel mode, which we call a hybrid construct (hybrid construct), which combines the advantages of both modes.

There are several mechanisms for thread synchronization in C #, which can usually be selected in the following order.

  1. If the code can be optimized without synchronizing, then do not synchronize.
  2. Use the atomic Interlocked method.
  3. Use the lock/Monitor class.
  4. Use asynchronous locks, such as SemaphoreSlim.WaitAsync() .
  5. Use other locking mechanisms, such as ReaderWriterLockSlim、Mutex、Semaphore .
  6. If the system provides a *Slim version of an asynchronous object, select it because the *Slim versions are all mixed locks and implement some sort of spin before entering kernel mode.

In the synchronization, we must pay attention to avoid the occurrence of deadlocks, the occurrence of deadlocks must meet the following 4 basic conditions, so only need to destroy any one of the conditions, you can avoid the occurrence of deadlocks.

  1. Exclusive or mutually exclusive (Mutual exclusion): one thread (Threada) is exclusive of one resource, and no other thread (THREADB) can get the same resource.
  2. Occupy and wait (Hold and wait): one of the mutually exclusive threads (Threada) requests to get the resources that another thread (THREADB) occupies.
  3. Not preemptive (no preemption): a thread (Threada) occupies a resource that cannot be forcibly taken away (only wait for Threada to voluntarily release its resources).
  4. Cyclic wait condition (Circular wait condition): Two or more threads form a loop wait chain, which locks two or more identical resources, each of which waits for the resource to be occupied by the next thread in the chain.
1.2 Performing basic atomic operations

The CLR guarantees that reads and writes to these data types are atomic: Boolean、Char、(S)Byte、(U)Int16、(U)Int32、(U)IntPtr和Single . However, if read/write is likely to occur, there is a Int64 problem with reading tearing (torn read), because it requires two operations in a 32-bit operating system Mov and cannot be completed within a single time.

In this section, you will focus on the System.Threading.Interlocked methods provided by the class, where Interlocked each method in the class performs a read and write operation. More Interlocked information related to the class, please refer to the link, poke a stamp This article is not repeat.

The demo code looks like this, using three different ways to count: Error count, lock, lock and Interlocked Atomic.

private static void Main (string[] args) {Console.WriteLine ("Count of Errors");    var c = new Counter ();    Execute (c);    Console.WriteLine ("--------------------------");    Console.WriteLine ("Correct count-with lock");    var C2 = new Counterwithlock ();    Execute (C2);    Console.WriteLine ("--------------------------");    Console.WriteLine ("Correct count-no lock");    var C3 = new Counternolock ();    Execute (C3); Console.ReadLine ();}    static void Execute (Counterbase c) {//Statistic time-consuming var sw = new Stopwatch (); Sw.    Start ();    var T1 = new Thread (() = Testcounter (c));    var t2 = new Thread (() = Testcounter (c));    var t3 = new Thread (() = Testcounter (c)); T1.    Start (); T2.    Start (); T3.    Start (); T1.    Join (); T2.    Join (); T3.    Join (); Sw.    Stop (); Console.WriteLine ($ "total count: {C.count} time:{sw. Elapsedmilliseconds} MS ");}        static void Testcounter (Counterbase c) {for (int i = 0; i < 100000; i++) {c.increment ();    C.decrement (); }}class counter:counterbase{PUBlic override void Increment () {_count++;    } public override void Decrement () {_count--; }}class counternolock:counterbase{public override void Increment () {//Use interlocked to perform atomic operations Interlo Cked.    Increment (ref _count);    } public override void Decrement () {interlocked.decrement (ref _count);    }}class counterwithlock:counterbase{Private ReadOnly Object _syncroot = new Object ();            public override void Increment () {//Lock private variable with lock keyword Lock (_syncroot) {//sync block        count++;        }} public override void Decrement () {lock (_syncroot) {count--;    }}}abstract class counterbase{protected int _count;        public int Count {get {return _count;        } set {_count = value;    }} public abstract void Increment (); public abstract void decrement ();}

The results of the operation are as follows and are basically consistent with the expected results.

1.3 Using the Mutex class

System.Threading.MutexConceptually and System.Threading.Monitor almost the same, but the Mutex synchronization of access to files or other cross-process resources, that Mutex is, can be cross-process. Because of its nature, one purpose of it is to restrict applications from running multiple instances at the same time.

MutexThe object supports recursion, which means that the same thread can get the same lock multiple times , which is observable in the following demo code. Because Mutex the base class System.Theading.WaitHandle implements the IDisposable interface, it is not necessary to pay attention to the release of resources when it is used. More information: poke a poke

The demo code looks like this, and simply demonstrates how to create a single-instance application and a Mutex recursive get lock implementation.

const string MutexName = "CSharpThreadingCookbook";static void Main(string[] args){    // 使用using 及时释放资源    using (var m = new Mutex(false, MutexName))    {        if (!m.WaitOne(TimeSpan.FromSeconds(5), false))        {            Console.WriteLine("已经有实例正在运行!");        }        else        {            Console.WriteLine("运行中...");            // 演示递归获取锁            Recursion();            Console.ReadLine();            m.ReleaseMutex();        }    }    Console.ReadLine();}static void Recursion(){    using (var m = new Mutex(false, MutexName))    {        if (!m.WaitOne(TimeSpan.FromSeconds(2), false))        {            // 因为Mutex支持递归获取锁 所以永远不会执行到这里            Console.WriteLine("递归获取锁失败!");        }        else        {            Console.WriteLine("递归获取锁成功!");        }    }}

As shown in the running results, two applications were opened because a Mutex single instance was implemented, so the second application could not acquire the lock and it would show that an existing instance was running .

1.4 Using the Semaphoreslim class

SemaphoreSlimClass has a different lock than the previously mentioned synchronization class, the previously mentioned synchronization classes are mutually exclusive, meaning that only one thread is allowed to access the resource, and SemaphoreSlim multiple accesses are allowed.

In the previous section, it was mentioned that the *Slim thread synchronization classes at the end are all working in mixed mode, that is, they start with "spin" in user mode, and then switch to kernel mode when the first competition occurs. But SemaphoreSlim unlike Semaphore a class, it does not support system semaphores, so it cannot be used for synchronization between processes .

This class is simpler to use, and the demo code demonstrates that 6 threads compete for access to a database that only allows 4 threads to access at the same time, as shown below.

static void Main(string[] args){    // 创建6个线程 竞争访问AccessDatabase    for (int i = 1; i <= 6; i++)    {        string threadName = "线程 " + i;        // 越后面的线程,访问时间越久 方便查看效果        int secondsToWait = 2 + 2 * i;        var t = new Thread(() => AccessDatabase(threadName, secondsToWait));        t.Start();    }    Console.ReadLine();}// 同时允许4个线程访问static SemaphoreSlim _semaphore = new SemaphoreSlim(4);static void AccessDatabase(string name, int seconds){    Console.WriteLine($"{name} 等待访问数据库.... {DateTime.Now.ToString("HH:mm:ss.ffff")}");    // 等待获取锁 进入临界区    _semaphore.Wait();    Console.WriteLine($"{name} 已获取对数据库的访问权限 {DateTime.Now.ToString("HH:mm:ss.ffff")}");    // Do something    Thread.Sleep(TimeSpan.FromSeconds(seconds));    Console.WriteLine($"{name} 访问完成... {DateTime.Now.ToString("HH:mm:ss.ffff")}");    // 释放锁    _semaphore.Release();}

The result of the operation is as follows, the first 4 threads immediately acquire the lock, enter the critical section, while the other two threads are waiting, and when a lock is released, it can enter the critical section.

1.5 Using the AutoResetEvent class

AutoResetEventCalled an auto-reset event, although the name has the word event , but the reset event has nothing to do with the delegate in C #, where the event is only a variable maintained by the kernel Boolean , and when the event is false , the thread waiting on the event is blocked, and the event becomes true , then the blocking is lifted.

There are two such events in. NET, namely, AutoResetEvent(自动重置事件) and ManualResetEvent(手动重置事件) . Both are kernel-mode , and the difference is that when resetting true an event, it automatically resets the event to wake up only one blocked thread, automatically resets the event back to false, causing the other threads to continue blocking. manual reset events are not automatically reset and must be manually reset back to false through code.

Because of the above reasons, it is not recommended to use in many articles and books AutoResetEvent(自动重置事件) because it is easy to make mistakes when writing producer threads, causing it to iterate more times than the consumer thread.

The demo code is shown below, which demonstrates the AutoResetEvent synchronization of two threads by implementing one another.

static void Main (string[] args) {var t = new Thread (() + = Process (10));    T.start (); Console.WriteLine ("Wait for another thread to finish working!")    ");    Wait for the worker thread to notify the main thread to block _workerevent.waitone (); Console.WriteLine ("The first operation has been completed!    ");    Console.WriteLine ("Perform operations on the main thread");    Thread.Sleep (Timespan.fromseconds (5));    Send notification worker thread to continue running _mainevent.set ();    Console.WriteLine ("Now run the second operation on the second thread");    Wait for the worker thread to notify the main thread to block _workerevent.waitone (); Console.WriteLine ("The second operation is complete!    "); Console.ReadLine ();} Worker thread Eventprivate static AutoResetEvent _workerevent = new AutoResetEvent (false);//main thread eventprivate static AutoResetEvent _mainevent = new AutoResetEvent (false); static void Process (int seconds) {Console.WriteLine ("Start long work ...    ");    Thread.Sleep (timespan.fromseconds (seconds));    Console.WriteLine ("Work done!");    Send the notification main thread to continue running _workerevent.set ();    Console.WriteLine ("Wait for the main thread to complete other work");    Wait for the main thread to notify the worker thread to block _mainevent.waitone ();    Console.WriteLine ("Start the second operation ..."); Thread.Sleep (timespan.fromseconds (seconds));    Console.WriteLine ("Work done!"); Send the notification main thread to continue running _workerevent.set ();}

The results of the operation are as shown and conform to the expected results.

1.6 Using the ManualResetEventSlim class

ManualResetEventSlimThe use and ManualResetEvent class are basically consistent, just ManualResetEventSlim work in mixed mode , and it AutoResetEventSlim differs from the need to manually reset the event, that is, the call to reset the Reset() event to false .

The demo code is as follows, and the image will be likened to the ManualResetEventSlim gate, when the event is true opened, the thread is unblocked, and the event is false closed and the thread is blocked.

static void Main (string[] args) {var T1 = new Thread (() = Travelthroughgates ("Thread 1", 5));            var t2 = new Thread (() = Travelthroughgates ("Thread 2", 6));            var t3 = new Thread (() = Travelthroughgates ("Thread 3", 12)); T1.            Start (); T2.            Start (); T3.            Start ();             Sleep for 6 seconds only thread 1 is less than 6 seconds, so when the event resets thread 1 must be able to enter the gate and thread 2 may be able to enter the gate Thread.Sleep (timespan.fromseconds (6));  Console.WriteLine ($ "The gate is now open!            Time: {DateTime.Now.ToString ("Mm:ss.ffff")} ");            _mainevent.set ();            Sleeps for 2 seconds at this point Thread 2 must be able to enter the gate Thread.Sleep (Timespan.fromseconds (2));            _mainevent.reset ();            Console.WriteLine ($ "The gate is now closed!" Time: {DateTime.Now.ToString ("Mm:ss.ffff")} ");            Sleep for 10 seconds Thread 3 can enter the gate Thread.Sleep (Timespan.fromseconds (10));            Console.WriteLine ($ "The gate now opens for the second time!" Time: {DateTime.Now.ToString ("Mm:ss.ffff")} ");   _mainevent.set ();         Thread.Sleep (Timespan.fromseconds (2));            Console.WriteLine ($ "The gate is now closed!" Time: {DateTime.Now.ToString ("Mm:ss.ffff")} ");            _mainevent.reset ();        Console.ReadLine (); } static void Travelthroughgates (string threadname, int seconds) {Console.WriteLine ($ "{Threadnam            e} Enter sleep time: {DateTime.Now.ToString ("Mm:ss.ffff")} ");            Thread.Sleep (timespan.fromseconds (seconds));            Console.WriteLine ($ "{threadname} wait for the gate to open! Time: {DateTime.Now.ToString (" Mm:ss.ffff ")}");            _mainevent.wait ();        Console.WriteLine ($ "{ThreadName} enter the gate! Time: {DateTime.Now.ToString (" Mm:ss.ffff ")}"); } static ManualResetEventSlim _mainevent = new ManualResetEventSlim (false);

The results of the operation are as follows, consistent with expected results.

1.7 Using the Countdownevent class

CountDownEventAn object is used within the class inner construct ManualResetEventSlim . This construct blocks a thread until its internal counter becomes active (CurrentCount) 0 before unblocking. This means that it does not prevent access to a resource pool that has been depleted, but only when the count is 0 .

It is important to note that when CurrentCount 0 it becomes, it cannot be changed. For 0 later, Wait() the blocking of the method is relieved.

The demo code looks like this, and Signal() Wait() the method's blocking is dismissed only after the method has been called 2 times.

static void Main(string[] args){    Console.WriteLine($"开始两个操作  {DateTime.Now.ToString("mm:ss.ffff")}");    var t1 = new Thread(() => PerformOperation("操作 1 完成!", 4));    var t2 = new Thread(() => PerformOperation("操作 2 完成!", 8));    t1.Start();    t2.Start();    // 等待操作完成    _countdown.Wait();    Console.WriteLine($"所有操作都完成  {DateTime.Now.ToString("mm: ss.ffff")}");    _countdown.Dispose();    Console.ReadLine();}// 构造函数的参数为2 表示只有调用了两次 Signal方法 CurrentCount 为 0时  Wait的阻塞才解除static CountdownEvent _countdown = new CountdownEvent(2);static void PerformOperation(string message, int seconds){    Thread.Sleep(TimeSpan.FromSeconds(seconds));    Console.WriteLine($"{message}  {DateTime.Now.ToString("mm:ss.ffff")}");    // CurrentCount 递减 1    _countdown.Signal();}

As shown in the running results, it is visible that all operations are completed only after Operation 1 and Operation 2 are complete.

1.8 Using the Barrier class

BarrierClass is used to solve a very rare problem, usually not used. Barrierclass controls a series of threads for periodic parallel work.

Assuming that the parallel work is divided into 2 stages, each thread must stop to wait for the other thread to complete the work of Phase 1 after completing its own portion of Phase 1, and after all the threads have completed Phase 1 work, each thread starts running, completes phase 2, waits for all other threads to complete phase 2 work, The whole process is not over.

The demo code is shown below, which demonstrates the phased completion of two threads.

static void Main(string[] args){    var t1 = new Thread(() => PlayMusic("钢琴家", "演奏一首令人惊叹的独奏曲", 5));    var t2 = new Thread(() => PlayMusic("歌手", "唱着他的歌", 2));    t1.Start();    t2.Start();    Console.ReadLine();}static Barrier _barrier = new Barrier(2, Console.WriteLine($"第 {b.CurrentPhaseNumber + 1} 阶段结束"));static void PlayMusic(string name, string message, int seconds){    for (int i = 1; i < 3; i++)    {        Console.WriteLine("----------------------------------------------");        Thread.Sleep(TimeSpan.FromSeconds(seconds));        Console.WriteLine($"{name} 开始 {message}");        Thread.Sleep(TimeSpan.FromSeconds(seconds));        Console.WriteLine($"{name} 结束 {message}");        _barrier.SignalAndWait();    }}

The results are as follows, and when the "singer" thread finishes, it does not end immediately, but instead waits for the "pianist" thread to end, and when the "pianist" thread finishes, the 2nd phase of work begins.

1.9 Using the ReaderWriterLockSlim class

ReaderWriterLockSlimClass is mainly to solve in some scenarios, read more than write operations and use some mutex when multiple threads access resources at the same time, only one thread can access, resulting in a sharp decline in performance.

If all threads want to access the data in a read-only manner, there is no need to block them at all, and if a thread wants to modify the data, the thread needs exclusive access, which is ReaderWriterLockSlim typical of the scenario. This class is like the following to control threads.

  • One thread writes to the data, and all other threads that request access are blocked.
  • When a thread reads data, the thread that requests the read is allowed to read, and the thread that requests the write is blocked.
  • After the write thread ends, either a write thread is unblocked, the write line Cheng Nen to the data, or all the read threads are unblocked so that they can read the data concurrently. If the thread is not blocked, the lock can go into a free-to-use state that can be fetched by the next read or write thread.
  • After all threads read from the data end, a write thread is unblocked so that it can write to the data. If the thread is not blocked, the lock can go into a free-to-use state that can be fetched by the next read or write thread.

ReaderWriterLockSlimIt also supports upgrading from a read thread to a write thread , with the details stamped. The text is not described. ReaderWriterLockclass is obsolete, and there are many problems that are not necessary to use.

The sample code is as follows, creating 3 read threads, 2 write threads, read threads and write threads competing to acquire locks.

static void Main (string[] args) {//create 3 read thread new Thread (() = Read ("Reader 1")) {IsBackground = true}.    Start (); New Thread (() = Read ("Reader 2") {IsBackground = true}.    Start (); New Thread (() = Read ("Reader 3") {IsBackground = true}.    Start (); Create two write threads new Thread (() = Write ("Writer 1")) {IsBackground = true}.    Start (); New Thread (() = Write ("Writer 2")) {IsBackground = true}.    Start ();    Make the program run 30S Thread.Sleep (Timespan.fromseconds (30)); Console.ReadLine ();} static ReaderWriterLockSlim _RW = new ReaderWriterLockSlim (), Static dictionary<int, int> _items = new Dictionary&lt            ; int, int> (), static void Read (string threadname) {while (true) {try {//Get read lock _RW.            EnterReadLock ();            Console.WriteLine ($ "{ThreadName} reads the contents {DateTime.Now.ToString (" Mm:ss.ffff ")} from the dictionary); foreach (Var key in _items.           Keys) {Thread.Sleep (Timespan.fromseconds (0.1)); }} finally {//release read lock _RW.        Exitreadlock (); }}}static void Write (String threadname) {while (true) {try {int newKey = new Random () .            Next (250); Attempt to enter upgradeable lock mode state _RW.            Enterupgradeablereadlock (); if (!_items. ContainsKey (NewKey)) {try {//Get write lock _RW.E                    Nterwritelock ();                    _items[newkey] = 1;                Console.WriteLine ($ "{ThreadName} adds the new key {NewKey} into the dictionary {DateTime.Now.ToString (" Mm:ss.ffff ")}"); } finally {//releases the write lock _RW.                Exitwritelock ();        }} thread.sleep (Timespan.fromseconds (0.1)); The finally {//reduces the upgradeable mode recursive count, and launches upgradeable mode _RW at the count of 0 o'clock.        Exitupgradeablereadlock (); }    }}

The results of the operation are as follows and match the expected results.

1.10 Using the SpinWait class

SpinWaitis a commonly used mixed-mode class that is designed to use user mode to wait for a period of time after a person switches to kernel mode to save CPU time.

It is very simple to use and the demo code is shown below.

static void Main(string[] args){    var t1 = new Thread(UserModeWait);    var t2 = new Thread(HybridSpinWait);    Console.WriteLine("运行在用户模式下");    t1.Start();    Thread.Sleep(20);    _isCompleted = true;    Thread.Sleep(TimeSpan.FromSeconds(1));    _isCompleted = false;    Console.WriteLine("运行在混合模式下");    t2.Start();    Thread.Sleep(5);    _isCompleted = true;    Console.ReadLine();}static volatile bool _isCompleted = false;static void UserModeWait(){    while (!_isCompleted)    {        Console.Write(".");    }    Console.WriteLine();    Console.WriteLine("等待结束");}static void HybridSpinWait(){    var w = new SpinWait();    while (!_isCompleted)    {        w.SpinOnce();        Console.WriteLine(w.NextSpinWillYield);    }    Console.WriteLine("等待结束");}

As shown in the two figure below, the program first runs in simulated user mode, causing the CPU to have a short spike. Then use the SpinWait work in mixed mode, first the flag variable is in False user mode spin, wait to enter kernel mode later.

Reference books

This article mainly refers to the following books, here to express our heartfelt thanks to you for providing such good information.

  1. "CLR via C #"
  2. "C # in Depth third Edition"
  3. "Essential C # 6.0"
  4. "Multithreading with C # Cookbook Second Edition"

Source Download Click link sample source download

The author level is limited, if the mistake welcome everybody criticism correct!

C # multithreaded Programming series (iii)-thread synchronization

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.