C#多线程.mmap

02 线程同步 - 图2

C#线程同步技术区别
内核模式不能等待较长时间,吃CPU资源。

2.1 原子操作Interlocked(数学运算)

直接使用原子方法Interlocked.数学运算(),来避免使用锁。

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading;
  6. namespace InterlockedExample {
  7. class Program {
  8. static void Main(string[] args) {
  9. var c = new CounterWithoutInter();
  10. Thread threadOne = new Thread(() => TestCounter(c));
  11. Thread threadTwo = new Thread(() => TestCounter(c));
  12. threadOne.Start();
  13. threadTwo.Start();
  14. threadOne.Join();
  15. threadTwo.Join();
  16. Console.WriteLine(c.Count+"\n");
  17. var cNew = new CounterWihtInterLocked();
  18. Thread threadThree = new Thread(() => TestCounter(cNew));
  19. Thread threadFour = new Thread(() => TestCounter(cNew));
  20. threadThree.Start();
  21. threadFour.Start();
  22. threadThree.Join();
  23. threadFour.Join();
  24. Console.WriteLine(cNew.Count + "\n");
  25. Console.ReadLine();
  26. }
  27. static void TestCounter(CounterBase c) {
  28. for (int i = 0; i < 100000; i++) {
  29. c.Increment();
  30. c.Decrement();
  31. }
  32. }
  33. }
  34. class CounterWithoutInter : CounterBase {
  35. private int _count;
  36. public int Count => _count;
  37. public override void Decrement() {
  38. _count--;
  39. }
  40. public override void Increment() {
  41. _count++;
  42. }
  43. }
  44. class CounterWihtInterLocked : CounterBase {
  45. private int _count;
  46. public int Count => _count;
  47. public override void Decrement() {
  48. Interlocked.Decrement(ref _count);
  49. }
  50. public override void Increment() {
  51. Interlocked.Increment(ref _count);
  52. }
  53. }
  54. abstract class CounterBase {
  55. public abstract void Increment();
  56. public abstract void Decrement();
  57. }
  58. }

2.2 Mutex类(互斥锁,进程间同步)

2.2.1类理解

Mutex 中文为互斥,Mutex 类叫做互斥锁。它还可用于进程间同步的同步基元。Mutex 跟 lock 相似,但是 Mutex 支持多个进程。Mutex 大约比 lock 慢 20 倍。
第一个参数initiallyOwned代表刚开始厕所是否有人,如果为true则有人,可以ReleaseMutex(),不会报错
第二个参数,构造函数中,如果为 name 指定 null 或空字符串,则将创建一个本地 Mutex 对象,只会在进程内有效。如果不加Global则默认在Local有效。
互斥锁(Mutex):用于多个线程中防止两条线程同时对一个公共资源对象进行读写的机制。
Windows 操作系统中,Mutex 同步对象有两个状态:

  • signaled:未被任何对象拥有;
  • nonsignaled:被一个线程拥有;

Mutex 只能在获得锁的线程中,释放锁。
Mutex 类其构造函数如下:

构造函数 说明
Mutex() 使用默认属性初始化 Mutex类的新实例。
Mutex(Boolean) 使用 Boolean 值(指示调用线程是否应具有互斥体的初始所有权)初始化 Mutex 类的新实例。
Mutex(Boolean, String) 使用 Boolean 值(指示调用线程是否应具有互斥体的初始所有权以及字符串是否为互斥体的名称)初始化 Mutex 类的新实例。
Mutex(Boolean, String, Boolean) 使用可指示调用线程是否应具有互斥体的初始所有权以及字符串是否为互斥体的名称的 Boolean 值和当线程返回时可指示调用线程是否已赋予互斥体的初始所有权的 Boolean 值初始化 Mutex 类的新实例。

Mutex 对于进程同步有所帮助,例如其应用场景主要是控制系统只能运行一个此程序的实例。
Mutex 的常用方法如下:

方法 说明
Close() 释放由当前 WaitHandle 占用的所有资源。
Dispose() 释放由 WaitHandle 类的当前实例占用的所有资源。
OpenExisting(String) 打开指定的已命名的互斥体(如果已经存在)。
ReleaseMutex() 释放 Mutex一次。
TryOpenExisting(String, Mutex) 打开指定的已命名的互斥体(如果已经存在),并返回指示操作是否成功的值。
WaitOne() 阻止当前线程,直到当前 WaitHandle 收到信号。
WaitOne(Int32) 阻止当前线程,直到当前 WaitHandle 收到信号,同时使用 32 位带符号整数指定时间间隔(以毫秒为单位)。
WaitOne(Int32, Boolean) 阻止当前线程,直到当前的 WaitHandle 收到信号为止,同时使用 32 位带符号整数指定时间间隔,并指定是否在等待之前退出同步域。
WaitOne(TimeSpan) 阻止当前线程,直到当前实例收到信号,同时使用 TimeSpan 指定时间间隔。
WaitOne(TimeSpan, Boolean) 阻止当前线程,直到当前实例收到信号为止,同时使用 TimeSpan 指定时间间隔,并指定是否在等待之前退出同步域。

2.2.2类实例

具名的互斥量是全局的操作系统,请务必正确的关闭互斥量,最好使用using代码块来包裹互斥量对象。
(1)系统只能运行一个程序

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading;
  6. namespace MutexOne {
  7. class Program {
  8. const string MutexName = "CSharp";
  9. private static Mutex m;
  10. static void Main(string[] args) {
  11. bool firstInstance;
  12. using (m = new Mutex(false, MutexName, out firstInstance)) {
  13. if (!firstInstance) { //非第一次调用
  14. Console.WriteLine("程序已经在执行,等待两秒将直接退出本程序");
  15. Thread.Sleep(2000);
  16. return;
  17. }
  18. Console.WriteLine("程序正在启动....\n");
  19. Console.WriteLine("程序运行中....\n");
  20. Console.WriteLine("请按回车键退出运行");
  21. Console.ReadLine();
  22. m.Close();//非必要,出了using就销毁了资源
  23. return;
  24. }
  25. }
  26. }
  27. }

(2)厕所蹲坑,排队运行
第一个参数initiallyOwned代表刚开始厕所是否有人,如果为true则有人,可以ReleaseMutex(),不会报错

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace MutexTwo {
    class Program {
        const string MutexName = "CSharp";
        private static Mutex m;
        static void Main(string[] args) {
            bool firstInstance;
            //第一个参数initiallyOwned代表刚开始厕所是否有人,如果为true则有人,可以ReleaseMutex(),不会报错
            using (m = new Mutex(true, MutexName, out firstInstance)) {
                if (!firstInstance) { //有人lashi
                    Console.WriteLine("有人lashi");
                    m.WaitOne();//排队等待
                    GoToWC();
                    return;
                }
                GoToWC();
                return;
            }
        }

        private static void GoToWC() {
            Console.WriteLine("准备上WC");
            Thread.Sleep(1000);
            Console.WriteLine("占坑");
            Console.WriteLine("xxxxxx");
            Thread.Sleep(2000);
            m.ReleaseMutex();//出厕所
            Console.WriteLine("洗手");
            Thread.Sleep(1000);
        }
    }
}

(3)父子进程同步退出
子进程:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace MutexChild {
    class Program {
        const string name = "MutexTest";
        private static Mutex mutex;
        static void Main(string[] args) {
            Console.WriteLine("子线程被启动...");
            bool firstInstance;
            mutex = new Mutex(true, name, out firstInstance);
            if (firstInstance) {
                Console.WriteLine("子线程执行任务");
                DoWork();
                Console.WriteLine("子线程任务完成");
                Console.ReadLine();
                mutex.ReleaseMutex();//有坑位才能ReleaseMutex
                return;
            }
            Console.WriteLine("有子程序还在执行中");
            Console.ReadLine();
            Thread.Sleep(5000);
            return;
        }
        private static void DoWork() {
            for (int i = 0; i < 5; i++) {
                Console.WriteLine("子线程工作中....");
                TimeSpan.FromSeconds(3);
            }
        }
    }
}

父进程:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Diagnostics;

namespace MutexParent {
    class Program {
        const string name = "MutexTest";
        private static Mutex mutex;
        static void Main(string[] args) {
            Console.WriteLine("父进程启动!");
            new Thread(() => {
                Process process = new Process();
                //是否使用操作系统外壳程序启动进程,如果使用外壳程序则为true,否则直接执行可执行文件创建进程。
                //UseShellExecute 在 .NET Framework 中的的默认值是 true,在 .NET Core 中的默认值是 false。

                //如果有以下需求,那么建议设置此值为 false:
                //需要明确执行一个已知的程序
                //需要重定向输入和输出

                //如果你有以下需求,那么建议设置此值为 true 或者保持默认:
                //需要打开文档、媒体、网页文件等
                //需要打开 Url
                //需要打开脚本执行
                //需要打开计算机上环境变量中路径中的程序

                process.StartInfo.UseShellExecute = true;
                process.StartInfo.CreateNoWindow = true;
                process.StartInfo.WorkingDirectory = @"../../../MutexChild\bin\Debug";
                process.StartInfo.FileName = @"../../../MutexChild\bin\Debug\MutexChild.exe";
                process.Start();
                process.WaitForExit();
            }).Start();

            Thread.Sleep(1000);
            bool firstInstance;
            mutex = new Mutex(false, name, out firstInstance);
            if (!firstInstance) {
                Console.WriteLine("等待子线程执行");
                mutex.WaitOne();
                Console.WriteLine("子线程运行结束");
                mutex.ReleaseMutex();
                return;
            }
        }
    }
}

2.3 SemaphoreSlim类(线程间同步,混合模式)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace SemaphoreSlimExample {
    class Program {
        static SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(4);//4个可用的位置
        static void Main(string[] args) {
            for (int i = 0; i < 6; i++) {
                new Thread(() => {
                    Thread.CurrentThread.Name = "Thread" + i.ToString();
                    AccessDatabase(Thread.CurrentThread.Name.ToString());
                }).Start();
            }
            Console.ReadLine();
        }

        static void AccessDatabase(string name) {
            Console.WriteLine($"{name} is running");
            _semaphoreSlim.Wait();
            Console.WriteLine($"{name} can use.");
            TimeSpan.FromSeconds(3);
            Console.WriteLine($"{name} is ok");
            _semaphoreSlim.Release();
        }
    }
}

2.4AutoResetEvent类(内核模式,线程间发送通知)

2.5ManualResetEvent类(混合模式,进阶版,发送通知)

Wait()等待大门打开
Set()大门打开,一群人都可以通过
Reset()大门关闭,没有进去的等待下一次开门

using System;
using System.Threading;

namespace ManualResetEventSlimExample {
    class Program {
        static ManualResetEventSlim _manualResetEvent = new ManualResetEventSlim();
        static void Main(string[] args) {
            new Thread(() => TravelThroughGates("Thread1", 2)).Start();
            new Thread(() => TravelThroughGates("Thread2", 6)).Start();
            Thread.Sleep(3000);
            _manualResetEvent.Set();//开门,允许通过,此时线程1,会继续执行,进门成功,但是线程2还没到进门时间
            Thread.Sleep(1000);
            _manualResetEvent.Reset();
            Thread.Sleep(5000);
            _manualResetEvent.Set();
            Console.WriteLine("全部进门成功");
            Thread.Sleep(1000);
            _manualResetEvent.Reset();
            Console.ReadLine();

        }
        static void TravelThroughGates(string ThreadName,int seconds) {
            Console.WriteLine($"{ThreadName} falls to sleep.");
            Thread.Sleep(TimeSpan.FromSeconds(seconds));
            Console.WriteLine($"{ThreadName} waits for the gates to open");
            _manualResetEvent.Wait();
            Console.WriteLine($"{ThreadName} enters the gates");
        }
    }
}

2.6CountDownEvent类(等待一定数量的操作完成)

Wait()等待所有操作完成,可设置超时,即cancel令牌
事先new 一个CountDownEvent类实例,等待操作数为2,子线程运行两次,抛出两个Signal(),主线程收到两个信号后开始运行。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace CountDownEventExample {
    class Program {
        //等待一定数量的操作完成,发出Signal()信号
        static CountdownEvent _countdownEvent = new CountdownEvent(3);
        static void Main(string[] args) {
            for (int i = 0; i < 2; i++) {
                new Thread(() => {
                    Thread.CurrentThread.Name = i.ToString();
                    Perform(Thread.CurrentThread.Name);
                }).Start();
            }
            _countdownEvent.Wait();//这边可以设置超时,防止一直等待
            //也可以设置cancel令牌取消
            Console.WriteLine("Main Thread is running.");
            Console.WriteLine("SubThread is complete.");
            Console.ReadLine();
        }
        static void Perform(string name) {
            TimeSpan.FromSeconds(3);
            Console.WriteLine($"{name} is running");
            _countdownEvent.Signal();
        }
    }
}

2.7Barrier类(多阶段,多线程迭代运算)

用于组织多个线程及时在某个时刻碰面。
Barrier(参与的线程数量,每个阶段完成之后执行)
两个线程开始执行,A B组一号选手都跑完,才能开始两组2号选手的跑步。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace BarrierExample {
    class Program {
        static Barrier _barrier = new Barrier(2, b => Console.WriteLine($"{b.CurrentPhaseNumber}"));

        static void Main(string[] args) {
            for (int i = 0; i < 2; i++) {
                new Thread(() => {
                    Preform();
                }).Start();
            }
            Console.ReadLine();
        }

        static void Preform() {
            for (int i = 0; i < 4; i++) {
                Console.WriteLine(i.ToString() + "is running!");
                TimeSpan.FromSeconds(2);
                _barrier.SignalAndWait();
                Console.WriteLine("下一组开始跑步");
                Thread.Sleep(2000);
            }
        }
    }
}

2.8ReaderWriterLockSlim类(共享独占锁)

using System;
using System.Collections.Generic;
using System.Threading;

namespace ReaderWriterLockSlimExample {
    class Program {
        static ReaderWriterLockSlim _rwLockSlim = new ReaderWriterLockSlim();
        static Dictionary<int, int> _items = new Dictionary<int, int>();

        static void Main(string[] args) {
            new Thread(Read) { IsBackground = true }.Start();
            new Thread(Read) { IsBackground = true }.Start();
            new Thread(Read) { IsBackground = true }.Start();
            new Thread(()=>Write("Thread Write 1")) { IsBackground = true }.Start();
            new Thread(() => Write("Thread Write 2")) { IsBackground = true }.Start();
            Console.ReadLine();
        }

        static void Read() {
            Console.WriteLine("Reading contents of a dictionary");
            while (true) {
                try {
                    _rwLockSlim.EnterReadLock();
                    foreach (var item in _items.Keys) {
                        Thread.Sleep(TimeSpan.FromSeconds(0.1));
                    }
                }
                catch (Exception) {

                    throw;
                }
                finally {
                    _rwLockSlim.ExitReadLock();
                }
            }
        }

        static void Write(string threadName) {
            while (true) {
                int newKey = new Random().Next(250);
                _rwLockSlim.EnterUpgradeableReadLock();
                if (!_items.ContainsKey(newKey)) {
                    try {
                        _rwLockSlim.EnterWriteLock();
                        _items[newKey] = 1;
                        Console.WriteLine($"{ newKey} is added to Dictionary by {threadName}");

                    }
                    catch (Exception) {

                        throw;
                    }
                    finally {
                        _rwLockSlim.ExitWriteLock();
                    }
                }
                _rwLockSlim.ExitUpgradeableReadLock();

            }
        }
    }
}