威尼斯人线上娱乐

线程同步各种锁,基元线程同步构造

31 3月 , 2019  

目录

多少个线程同时访问共享数据时,线程同步能制止数据损坏。之所以要强调还要,是因为线程同步难点莫过于正是计时题材。

多个线程同时访问共享数据时,线程同步能防备数据损坏。之所以要强调还要,是因为线程同步难题其实正是计时题材。

 1)原子操作(Interlocked):全体办法都以实施二遍原子读取或三回写入操作。

  • 1.1
    简介
  • 1.2
    执行基本原子操作
  • 1.3
    使用Mutex类
  • 1.4
    使用SemaphoreSlim类
  • 1.5
    使用AutoResetEvent类
  • 1.6
    使用ManualResetEventSlim类
  • 1.7
    使用CountDownEvent类
  • 1.8
    使用Barrier类
  • 1.9
    使用ReaderWriterLockSlim类
  • 1.10
    使用SpinWait类
  • 参考书籍
  • 小编水平有限,假设不当欢迎各位批评指正!

不供给线程同步是最特出的动静,因为线程同步一般很麻烦,涉及到线程同步锁的获得和假释,简单遗漏,而且锁会损耗品质,获取和刑释锁都亟需时间,最终锁的玩法就在于三回只可以让一个线程访问数据,那么就会卡住线程,阻塞线程就会让额外的线程发生,阻塞越来越多,线程越多,线程过多的弊端就不谈了。

不需求线程同步是最卓绝的气象,因为线程同步一般很麻烦,涉及到线程同步锁的收获和假释,不难遗漏,而且锁会损耗性能,获取和自由锁都亟待时间,最终锁的玩法就在于一回只好让一个线程访问数据,那么就会堵塞线程,阻塞线程就会让额外的线程产生,阻塞更多,线程更多,线程过多的坏处就不谈了。

  2)lock()语句:防止锁定public类型,不然实例将不止代码控制的范围,定义private对象来锁定。


线程同步各种锁,基元线程同步构造。由此能够幸免线程同步的话就相应去幸免,尽量不要去行使静态字段那样的共享数据。

据此能够制止线程同步的话就相应去幸免,尽量不要去选拔静态字段那样的共享数据。

  3)Monitor达成线程同步


类库和线程安全

类库和线程安全

    通过Monitor.Enter() 和
Monitor.Exit()达成排它锁的取得和刑释,获取之后独占财富,不容许任何线程访问。

1.1 简介

本章介绍在C#中贯彻线程同步的两种办法。因为多少个线程同时访问共享数据时,恐怕会促成共享数据的毁损,从而导致与预期的结果不合乎。为了消除这一个标题,所以需求用到线程同步,也被俗称为“加锁”。但是加锁相对不对进步品质,最多也便是不增不减,要兑现品质不增不减还得靠高品质的同步源语(Synchronization
Primitive)。不过因为没错永远比速度更首要,所以线程同步在少数场景下是必须的。

线程同步有二种源语(Primitive)构造:用户情势(user –
mode)
水源形式(kernel –
mode)
,当能源可用时间短的地方下,用户格局要优于根本格局,不过假如长日子无法获得财富,也许说长日子处于“自旋”,那么内核形式是相对来说好的取舍。

不过大家期望全部用户格局和基础情势的亮点,大家把它叫做掺杂构造(hybrid
construct)
,它具备了三种情势的独到之处。

在C#中有两种线程同步的编写制定,常常能够遵从以下顺序举行精选。

  1. 固然代码能因而优化能够不开始展览同步,那么就无须做一道。
  2. 动用原子性的Interlocked方法。
  3. 使用lock/Monitor类。
  4. 选用异步锁,如SemaphoreSlim.WaitAsync()
  5. 应用其它加锁机制,如ReaderWriterLockSlim、Mutex、Semaphore等。
  6. 一经系统提供了*Slim本子的异步对象,那么请接纳它,因为*Slim本子全体都以混合锁,在进入基础情势前落到实处了某种格局的自旋。

在一起中,一定要留心制止死锁的发生,死锁的发生必须满意以下四个为主尺度,所以只必要破坏任意叁个标准,就可幸免发生死锁。

  1. 排他或互斥(Mutual
    exclusion):三个线程(ThreadA)独占八个能源,没有别的线程(ThreadB)能获取相同的财富。
  2. 占用并等候(Hold and
    wait):互斥的三个线程(ThreadA)请求获取另一个线程(ThreadB)占有的能源.
  3. 不得超越(No
    preemption):四个线程(ThreadA)占有财富不能够被威逼拿走(只可以等待ThreadA主动释放它的能源)。
  4. 巡回等待条件(Circular wait
    condition):八个或八个线程构成一个巡回等待链,它们锁定四个或八个相同的财富,每种线程都在等待链中的下一个线程占有的能源。

.net类库保险了独具静态方法都以线程安全的,也正是说四个线程同时调用二个静态方法,不会生出多少被毁坏的动静。

.net类库保险了全体静态方法都以线程安全的,也正是说八个线程同时调用八个静态方法,不会时有发生多少被弄坏的景况。

    还有四个TryEnter方法,请求不到财富时不会堵塞等待,能够安装超时时间,获取不到一贯回到false。

1.2 执行基本原子操作

CL奥德赛保险了对这个数据类型的读写是原子性的:Boolean、Char、(S)Byte、(U)Int16、(U)Int32、(U)IntPtr和Single。可是若是读写Int64也许会时有爆发读取撕裂(torn
read)的标题,因为在叁十一位操作系统中,它要求履行五遍Mov操作,不可能在一个时光内推行到位。

那正是说在本节中,就会器重的介绍System.Threading.Interlocked类提供的方法,Interlocked类中的各样方法都以实行一回的读取以及写入操作。越多与Interlocked类相关的资料请参考链接,戳一戳.aspx)本文不在赘述。

演示代码如下所示,分别选用了三种办法进行计数:错误计数方式、lock锁格局和Interlocked原子形式。

private static void Main(string[] args)
{
    Console.WriteLine("错误的计数");

    var c = new Counter();
    Execute(c);

    Console.WriteLine("--------------------------");


    Console.WriteLine("正确的计数 - 有锁");

    var c2 = new CounterWithLock();
    Execute(c2);

    Console.WriteLine("--------------------------");


    Console.WriteLine("正确的计数 - 无锁");

    var c3 = new CounterNoLock();
    Execute(c3);

    Console.ReadLine();
}

static void Execute(CounterBase c)
{
    // 统计耗时
    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()
    {
        // 使用Interlocked执行原子操作
        Interlocked.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关键字 锁定私有变量
        lock (_syncRoot)
        {
            // 同步块
            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();
}

运作结果如下所示,与预期结果基本符合。

威尼斯人线上娱乐 1

并不能够确认保证全部实例方法线程安全。因为相似景色下实例创造后只有创造的线程能访问到,除非后来将实例的引用传给了一个静态变量,也许将引用传给了线程池的类别可能职分,那么此时或者就要考虑用线程同步了。

并无法担保拥有实例方法线程安全。因为相似情形下实例创造后唯有创设的线程能访问到,除非后来将实例的引用传给了一个静态变量,大概将引用传给了线程池的行列或许职责,那么此时大概就要考虑用线程同步了。

  4)ReaderWriterLock

1.3 使用Mutex类

System.Threading.Mutex在概念上和System.Threading.Monitor差一些一模一样,但是Mutex一道对文本也许别的跨进度的财富进行访问,也正是说Mutex是可跨进度的。因为其特点,它的3个用途是限制应用程序无法而且启动多个实例。

Mutex指标帮衬递归,相当于说同多少个线程可反复拿走同三个锁,那在末端演示代码中可观望到。由于Mutex的基类System.Theading.WaitHandle实现了IDisposable接口,所以当不要求在行使它时要专注进行能源的放飞。越来越多材质:戳一戳

以身作则代码如下所示,简单的以身作则了什么样创制单实例的应用程序和Mutex递归获取锁的达成。

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("递归获取锁成功!");
        }
    }
}

运行结果如下图所示,打开了四个应用程序,因为运用Mutex完结了单实例,所以首个应用程序不只怕获得锁,就会来得已有实例正在运营

威尼斯人线上娱乐 2

Console类包蕴一个静态字段,类的众多艺术都要收获和自由那么些目的上的锁,确定保证只有二个线程访问控制台。

Console类包蕴2个静态字段,类的很多措施都要赢得和刑释这几个目的上的锁,确认保证唯有3个线程访问控制台。

    当对能源操作读多写少的时候,为了增强能源的利用率,让读操作锁为共享锁,三个线程能够并发读取能源,而写操作为独占锁,只允许3个线程操作。

1.4 使用SemaphoreSlim类

SemaphoreSlim类与从前涉嫌的联手类有锁不相同,之前涉嫌的同步类都以排斥的,也正是说只同意1个线程实行访问能源,而SemaphoreSlim是能够允许多个访问。

在前头的部分有关联,以*Slim末段的线程同步类,都是工作在混合格局下的,也正是说开头它们都以在用户格局下”自旋”,等发生第三回竞争时,才切换成基础情势。可是SemaphoreSlim不同于Semaphore类,它不支持系统信号量,所以它不可能用于进度之间的一道

此类应用比较简单,演示代码演示了4个线程竞争访问只允许陆个线程同时做客的数据库,如下所示。

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();
}

运行结果如下所示,可知前四个线程登时就获得到了锁,进入了临界区,而其它多少个线程在伺机;等有锁被放出时,才能进入临界区。威尼斯人线上娱乐 3

基元用户格局和水源格局协会(这一局地看不领会能够先看看前边的用户方式和基本格局的教学,就会分晓了)

基元用户情势和基本情势组织(这一部分看不驾驭能够先看看前边的用户形式和水源情势的授课,就会知道了)

  5)事件(伊芙nt)类达成联机

1.5 使用AutoResetEvent类

AutoResetEvent叫自动重置事件,固然名称中有事件一词,可是重置事件和C#中的委托没有别的关联,这里的轩然大波只是由基本维护的Boolean变量,当事件为false,那么在事件上伺机的线程就卡住;事件变成true,那么阻塞解除。

在.Net中有二种此类事件,即AutoResetEvent(自动重置事件)ManualResetEvent(手动重置事件)。那两边均是采纳基础格局,它的区分在于当重置事件为true时,机动重置事件它只唤醒三个梗阻的线程,会自行将事件重置回false,造成别的线程继续阻塞。而手动重置事件不会自动重置,必须经过代码手动重置回false

因为以上的来头,所以在重重篇章和书籍中不引进应用AutoResetEvent(自动重置事件),因为它很不难在编辑生产者线程时发出失误,造成它的迭代次数多余消费者线程。

演示代码如下所示,该代码演示了通过AutoResetEvent落到实处四个线程的互相同步。

static void Main(string[] args)
{
    var t = new Thread(() => Process(10));
    t.Start();

    Console.WriteLine("等待另一个线程完成工作!");
    // 等待工作线程通知 主线程阻塞
    _workerEvent.WaitOne();
    Console.WriteLine("第一个操作已经完成!");
    Console.WriteLine("在主线程上执行操作");
    Thread.Sleep(TimeSpan.FromSeconds(5));

    // 发送通知 工作线程继续运行
    _mainEvent.Set();
    Console.WriteLine("现在在第二个线程上运行第二个操作");

    // 等待工作线程通知 主线程阻塞
    _workerEvent.WaitOne();
    Console.WriteLine("第二次操作完成!");

    Console.ReadLine();
}

// 工作线程Event
private static AutoResetEvent _workerEvent = new AutoResetEvent(false);
// 主线程Event
private static AutoResetEvent _mainEvent = new AutoResetEvent(false);

static void Process(int seconds)
{
    Console.WriteLine("开始长时间的工作...");
    Thread.Sleep(TimeSpan.FromSeconds(seconds));
    Console.WriteLine("工作完成!");

    // 发送通知 主线程继续运行
    _workerEvent.Set();
    Console.WriteLine("等待主线程完成其它工作");

    // 等待主线程通知 工作线程阻塞
    _mainEvent.WaitOne();
    Console.WriteLine("启动第二次操作...");
    Thread.Sleep(TimeSpan.FromSeconds(seconds));
    Console.WriteLine("工作完成!");

    // 发送通知 主线程继续运行
    _workerEvent.Set();
}

运转结果如下图所示,与预期结果符合。

威尼斯人线上娱乐 4

基元是指能够在代码中使用的最简易的结构。

基元是指能够在代码中接纳的最不难易行的布局。

    事件类有两种意况,终止情形和非终止状态,终止境况时调用WaitOne能够请求成功,通过Set将时刻状态设置为平息景况。

1.6 使用ManualResetEventSlim类

ManualResetEventSlim使用和ManualResetEvent类基本一致,只是ManualResetEventSlim工作在错落形式下,而它与AutoResetEventSlim今非昔比的地点就是内需手动重置事件,也正是调用Reset()才能将事件重置为false

示范代码如下,形象的将ManualResetEventSlim好比成大门,当事件为true时大门打开,线程解除阻塞;而事件为false时大门关闭,线程阻塞。

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();

            // 休眠6秒钟  只有Thread 1小于 6秒钟,所以事件重置时 Thread 1 肯定能进入大门  而 Thread 2 可能可以进入大门
            Thread.Sleep(TimeSpan.FromSeconds(6));
            Console.WriteLine($"大门现在打开了!  时间:{DateTime.Now.ToString("mm:ss.ffff")}");
            _mainEvent.Set();

            // 休眠2秒钟 此时 Thread 2 肯定可以进入大门
            Thread.Sleep(TimeSpan.FromSeconds(2));
            _mainEvent.Reset();
            Console.WriteLine($"大门现在关闭了! 时间:{DateTime.Now.ToString("mm: ss.ffff")}");

            // 休眠10秒钟 Thread 3 可以进入大门
            Thread.Sleep(TimeSpan.FromSeconds(10));
            Console.WriteLine($"大门现在第二次打开! 时间:{DateTime.Now.ToString("mm: ss.ffff")}");
            _mainEvent.Set();
            Thread.Sleep(TimeSpan.FromSeconds(2));

            Console.WriteLine($"大门现在关闭了! 时间:{DateTime.Now.ToString("mm: ss.ffff")}");
            _mainEvent.Reset();

            Console.ReadLine();
        }

        static void TravelThroughGates(string threadName, int seconds)
        {
            Console.WriteLine($"{threadName} 进入睡眠 时间:{DateTime.Now.ToString("mm:ss.ffff")}");
            Thread.Sleep(TimeSpan.FromSeconds(seconds));

            Console.WriteLine($"{threadName} 等待大门打开! 时间:{DateTime.Now.ToString("mm:ss.ffff")}");
            _mainEvent.Wait();

            Console.WriteLine($"{threadName} 进入大门! 时间:{DateTime.Now.ToString("mm:ss.ffff")}");
        }

        static ManualResetEventSlim _mainEvent = new ManualResetEventSlim(false);

运维结果如下,与预期结果符合。

威尼斯人线上娱乐 5

有三种基元构造:用户格局和基本形式。应尽恐怕利用基元用户格局结构,它们的速度明显大于内核格局的布局。

有两种基元构造:用户形式和基础格局。应尽或然采纳基元用户形式协会,它们的速度显著大于内核格局的结构。

    1)AutoReset伊芙nt(自动重置事件)

1.7 使用CountDownEvent类

CountDownEvent类内部组织采取了贰个ManualResetEventSlim目的。那几个组织阻塞三个线程,直到它当中计数器(CurrentCount)变为0时,才解除阻塞。也正是说它并不是阻挠对已经紧张的能源池的拜会,而是唯有当计数为0时才同意访问。

那边需求留意的是,当CurrentCount变为0威尼斯人线上娱乐,时,那么它就不能够被更改了。为0以后,Wait()艺术的梗塞被解除。

示范代码如下所示,只有当Signal()主意被调用二遍随后,Wait()艺术的围堵才被扫除。

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();
}

运营结果如下图所示,可知只有当操作1和操作2都完毕现在,才实施输出全数操作都成功。

威尼斯人线上娱乐 6

那是因为它们选择越发的CPU指令来协调线程,意味着和谐是在硬件上发出的,也象征操作系统永远检查和测试不到二个线程在基元用户情势的布局上过不去了。

那是因为它们接纳分外的CPU指令来协调线程,意味着和谐是在硬件上产生的,也意味操作系统永远检查和测试不到二个线程在基元用户形式的布局上围堵了。

    2)马努alReset伊芙nt(手动重置事件)

1.8 使用Barrier类

Barrier类用于消除二个很是稀有的难题,日常相像用不上。Barrier类控制一密密麻麻线程实行阶段性的互动工作。

要是现在互动工作分为三个阶段,每一种线程在成就它本人这某个等级1的办事后,必须停下来等待别的线程落成阶段1的干活;等有着线程均成功阶段1干活后,每种线程又开首运维,完结阶段2工作,等待别的线程全部完毕阶段2做事后,整个流程才甘休。

示范代码如下所示,该代码演示了七个线程分等级的做到工作。

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();
    }
}

运作结果如下所示,当“歌唱家”线程达成后,并没有及时停止,而是等待“钢琴家”线程停止,当”钢琴家”线程停止后,才开头第三阶段的行事。

威尼斯人线上娱乐 7

只有操作系统内核才能止住三个线程的运营。

只有操作系统内核才能止住一个线程的运作。

  6)信号量(Semaphore)

1.9 使用ReaderWriterLockSlim类

ReaderWriterLockSlim类首如若赶尽杀绝在一些场景下,读操作多于写操作而利用一些互斥锁当多个线程同时做客能源时,唯有多个线程能访问,导致性能小幅度降低。

借使持有线程都期待以只读的方法访问数据,就一贯没有要求阻塞它们;如若一个线程希望修改数据,那么那一个线程才供给独占访问,这正是ReaderWriterLockSlim的超级应用场景。那么些类就像是上面那样来决定线程。

  • 一个线程向数据写入是,请求访问的此外具无线程都被封堵。
  • 2个线程读取数据时,请求读取的线程允许读取,而请求写入的线程被封堵。
  • 写入线程停止后,要么解除三个写入线程的短路,使写入线程能向数据对接,要么解除所有读取线程的梗塞,使它们能并发读取多少。要是线程没有被卡住,锁就足以进入自由使用的景况,可供下一个读线程或写线程获取。
  • 从数据读取的有所线程截至后,贰个写线程被免除阻塞,使它能向数据写入。如若线程没有被封堵,锁就足以进去自由使用的场地,可供下多个读线程或写线程获取。

ReaderWriterLockSlim还帮助从读线程升级为写线程的操作,详情请戳一戳.aspx)。文本不作介绍。ReaderWriterLock类已经过时,而且存在许多题材,没有供给去采取。

示范代码如下所示,创建了二个读线程,三个写线程,读线程和写线程竞争获得锁。

static void Main(string[] args)
{
    // 创建3个 读线程
    new Thread(() => Read("Reader 1")) { IsBackground = true }.Start();
    new Thread(() => Read("Reader 2")) { IsBackground = true }.Start();
    new Thread(() => Read("Reader 3")) { IsBackground = true }.Start();

    // 创建两个写线程
    new Thread(() => Write("Writer 1")) { IsBackground = true }.Start();
    new Thread(() => Write("Writer 2")) { IsBackground = true }.Start();

    // 使程序运行30S
    Thread.Sleep(TimeSpan.FromSeconds(30));

    Console.ReadLine();
}

static ReaderWriterLockSlim _rw = new ReaderWriterLockSlim();
static Dictionary<int, int> _items = new Dictionary<int, int>();

static void Read(string threadName)
{
    while (true)
    {
        try
        {
            // 获取读锁定
            _rw.EnterReadLock();
            Console.WriteLine($"{threadName} 从字典中读取内容  {DateTime.Now.ToString("mm:ss.ffff")}");
            foreach (var key in _items.Keys)
            {
                Thread.Sleep(TimeSpan.FromSeconds(0.1));
            }
        }
        finally
        {
            // 释放读锁定
            _rw.ExitReadLock();
        }
    }
}

static void Write(string threadName)
{
    while (true)
    {
        try
        {
            int newKey = new Random().Next(250);
            // 尝试进入可升级锁模式状态
            _rw.EnterUpgradeableReadLock();
            if (!_items.ContainsKey(newKey))
            {
                try
                {
                    // 获取写锁定
                    _rw.EnterWriteLock();
                    _items[newKey] = 1;
                    Console.WriteLine($"{threadName} 将新的键 {newKey} 添加进入字典中  {DateTime.Now.ToString("mm:ss.ffff")}");
                }
                finally
                {
                    // 释放写锁定
                    _rw.ExitWriteLock();
                }
            }
            Thread.Sleep(TimeSpan.FromSeconds(0.1));
        }
        finally
        {
            // 减少可升级模式递归计数,并在计数为0时  推出可升级模式
            _rw.ExitUpgradeableReadLock();
        }
    }
}

运维结果如下所示,与预期结果符合。

威尼斯人线上娱乐 8

于是在用户格局下运作的线程或许被系统抢占。

就此在用户形式下运作的线程大概被系统抢占。

      信号量是由基本对象保证的int变量,为0时,线程阻塞,大于0时解除阻塞,当二个信号量上的等候线程解除阻塞后,信号量计数+1。

1.10 使用SpinWait类

SpinWait是3个常用的混杂方式的类,它被设计成采纳用户格局等待一段时间,人后切换至基本情势以节省CPU时间。

它的应用相当容易,演示代码如下所示。

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("等待结束");
}

运营结果如下两图所示,首先程序运营在模拟的用户方式下,使CPU有贰个短命的峰值。然后利用SpinWait做事在混合格局下,首先标志变量为False处于用户形式自旋中,等待现在进入基础形式。

威尼斯人线上娱乐 9

威尼斯人线上娱乐 10

之所以也足以用基本形式组织,因为线程通过基础形式的协会获取别的线程拥有的财富时,Windows会阻塞线程以幸免它浪费CPU时间。当能源变得可用时,Windows会复苏线程,允许它访问财富。

从而也得以用基本情势组织,因为线程通过基础格局的协会获取别的线程拥有的能源时,Windows会阻塞线程以免止它浪费CPU时间。当财富变得可用时,Windows会复苏线程,允许它访问财富。

      线程通过WaitOne将信号量减1,通过Release将信号量加1,使用很不难。

参考书籍

正文重要参考了以下几本书,在此对这一个小编表示真心的感激您们提供了那般好的材料。

  1. 《CLR via C#》
  2. 《C# in Depth Third Edition》
  3. 《Essential C# 6.0》
  4. 《Multithreading with C# Cookbook Second Edition》

源码下载点击链接
以身作则源码下载

不过线程从用户形式切换成基本形式(或相反)会造成巨大的质量损失。

唯独线程从用户情势切换来根本形式(或相反)会造成巨大的品质损失。

  7)互斥体(Mutex)

小编水平有限,借使不当欢迎各位批评指正!

对此在三个结构上等候的线程,假诺占有构造的那么些线程不自由它,前者就大概间接不通。构造是用户形式的组织意况下,线程会平昔在二个CPU上运营,称为“活锁”。假如是基本情势的结构,线程会平素不通,称为“死锁”。

对于在一个协会上伺机的线程,即使占有构造的那些线程不自由它,前者就恐怕间接不通。构造是用户形式的构造意况下,线程会一向在1个CPU上运维,称为“活锁”。假如是基本形式的布局,线程会一直不通,称为“死锁”。

      独占能源,用法与Semaphore相似。

死锁优于活锁,因为活锁既浪费CPU时间,又浪费内部存款和储蓄器,而死锁只浪费内部存款和储蓄器。

死锁优于活锁,因为活锁既浪费CPU时间,又浪费内部存款和储蓄器,而死锁只浪费内部存款和储蓄器。

   8)跨进程间的联合署名

而掺杂构造具有两者之长,在没有竞争的图景下,那几个协会异常的快且不会堵塞(如同用户格局的布局),在设有对组织的竞争的情状下,它会被操作系统内核阻塞。(下一章讲)

而掺杂构造具有两者之长,在并未竞争的情景下,这几个结构相当的慢且不会堵塞(就如用户模式的布局),在设有对结构的竞争的动静下,它会被操作系统内核阻塞。(下一章讲)

      通过安装同步对象的名目就足以兑现系统级的一路,分裂应用程序通过同步对象的名号识别区别同步对象。

用户形式组织

用户情势结构

CL路虎极光保险对以下数据类型的变量的读写是原子性的:Boolean,Char,S(Byte),U(Int16),U(Int32),U(IntPtr),Single以及引用类型。

CL普拉多保险对以下数据类型的变量的读写是原子性的:Boolean,Char,S(Byte),U(Int16),U(Int32),U(IntPtr),Single以及引用类型。

那代表变量中的全部字节都以贰回性读取或写入。(举个反例,对于一个Int64静态变量开首化为0,3个线程写它的时候只写了二分之一,另1个线程读取的时候读取到的是中间状态。然而话说回来,貌似陆十五个人机器2回性读取陆15人,是否在那个时候Int64也会编制程序原子性呢,未证实,可是不影响大家清楚。)

那表示变量中的全数字节都是一次性读取或写入。(举个反例,对于一个Int64静态变量早先化为0,1个线程写它的时候只写了大体上,另贰个线程读取的时候读取到的是中间状态。不过话说回来,貌似陆十一个人机器1次性读取六玖人,是或不是在那几个时候Int64也会编制程序原子性呢,未表明,不过不影响大家明白。)

本章讲解的基元用户格局社团就在于规划好这几个原子性数据的读取/写入时间。

本章讲解的基元用户形式组织就在于规划好那一个原子性数据的读取/写入时间。

实在这一个协会也得以强制为Int32和Double那个品种数据进行原子性的陈设好时间的拜会。

事实上那几个构造也可以强制为Int32和Double这么些项目数据开始展览原子性的宏图好时刻的拜会。

有两种基元用户形式线程同步构造

有二种基元用户形式线程同步构造

  • 易变构造
  • 互锁构造
  • 易变构造
  • 互锁构造

不无易变和互锁构造都供给传递对含蓄简单数据类型的多个变量的引用(内部存储器地址)。

富有易变和互锁构造都必要传递对含有不难数据类型的3个变量的引用(内部存款和储蓄器地址)。

易变构造

易变构造

在讲易变构造此前,得先讲二个标题,正是代码优化的标题。

在讲易变构造以前,得先讲三个标题,正是代码优化的标题。

事先我们讲过C#编写翻译器,JIT编写翻译器,CPU都恐怕会优化代码,典型的例证就是Timer的选取,一个提姆er对象在持续没有运用的景观下,大概直接被优化掉了,根本不会定时执行回调函数。

事先大家讲过C#编写翻译器,JIT编写翻译器,CPU都或然会优化代码,典型的例证正是Timer的运用,3个Timer对象在继续没有行使的情景下,或然直接被优化掉了,根本不会定时执行回调函数。

而这个优化成效是很难在调节和测试的时候看出来,因为调节和测试的时候并不曾对代码进行优化。

而那几个优化职能是很难在调节和测试的时候看出来,因为调节和测试的时候并从未对代码实行优化。

而二十四线程也会促成那样的难点,比如2个线程回调函数用到有些静态变量后,且并不改变那么些变量,那么大概就会进展优化,认为那几个变量的值不变,让其直接优化成固定的值。而你本来的目标实在另2个线程中改变这么些静态变量的值,以后你的改动也起不断效果看了。

而八线程也会造成那样的题材,比如多少个线程回调函数用到有些静态变量后,且并不改动这一个变量,那么恐怕就会开始展览优化,认为那几个变量的值不变,让其一贯优化成固定的值。而你当然的指标实在另三个线程中改变那个静态变量的值,未来您的变更也起绵绵效果看了。

再就是以下那样的代码而言或者因为代码的实行种种不相同而产出不止预期的结果。

同时以下那样的代码而言只怕因为代码的施行各种差别而出现抢先预想的结果。

        static int you = 0;
        static int me = 0;
        private static void Thread1() {
            me = 2;
            you = 2;
        }
        private static void Thread2()
        {
            if (you == 2) {
                Console.WriteLine(me);

        }    
        static int you = 0;
        static int me = 0;
        private static void Thread1() {
            me = 2;
            you = 2;
        }
        private static void Thread2()
        {
            if (you == 2) {
                Console.WriteLine(me);

        }    

像上边的代码,Thread1和Thread2方法分别在多个线程中循环运转。

像上边的代码,Thread1和Thread2方法分别在多个线程中循环运维。

根据大家推测的结果是,当Thread1运转完了,那么Thread2就会检查和测试到你2了,然后就打字与印刷自身是2.

服从我们预测的结果是,当Thread1运行完了,那么Thread2就会检查和测试到您2了,然后就打字与印刷本身是2.

而是因为编写翻译器优化的案由,you=2和me=2的相继完全是能够扭转的,那么超过写了you=2后,me=2那句代码还没实施,此时Thread2已经起来检查和测试到you==2了,那么此时打印的话,会议及展览示笔者不是2,是0.

只是因为编译器优化的由来,you=2和me=2的一一完全是足以反过来的,那么超越写了you=2后,me=2那句代码还没执行,此时Thread2已经上马检查和测试到you==2了,那么此时打字与印刷的话,会展现我不是2,是0.

照旧Thread第11中学的顺序没有变,而Thread第22中学的顺序变了,即you读取到数据和me读取到数据的代码也是足以被优化的,编写翻译器在Thread1未运营时,先读了me的值为0,而此时Thread1运营了,纵然给了me为2,可是线程2的寄存器中一度存为0了,所以未读取,那么此时结果照旧是您是2,而本人不是2;

要么Thread1中的顺序没有变,而Thread第22中学的顺序变了,即you读取到数据和me读取到数据的代码也是足以被优化的,编写翻译器在Thread1未运转时,先读了me的值为0,而此刻Thread1运维了,尽管给了me为2,但是线程2的寄存器中早已存为0了,所以未读取,那么此时结果照旧是您是2,而自小编不是2;

要消除那些标题就引入了大家的易变构造,这须求驾驭到多个静态类System.Threading.Volatile,它提供了多少个静态方法Write和Read。

要化解那一个题材就引入了大家的易变构造,那亟需精通到1个静态类System.Threading.Volatile,它提供了七个静态方法Write和Read。

这八个法子相比独特,它们会禁止C#编写翻译器,JIT编写翻译器和CPU平时执行的部分优化。

那三个艺术比较相当,它们会禁止C#编写翻译器,JIT编写翻译器和CPU平时执行的一部分优化。

切切实实的完成在于,Write方法会保险函数中,全体在Write方法在此之前实施的数目读写操作都在Write方法写入此前就实施了。

实际的贯彻在于,Write方法会保证函数中,全体在Write方法以前实施的数据读写操作都在Write方法写入此前就进行了。

而Read方法会保障函数中,全体在Read方法执行之后的多少读写操作,一定实在Read方法执行后才开始展览。

而Read方法会保险函数中,全体在Read方法执行之后的数额读写操作,一定实在Read方法执行后才开始展览。

修改代码后

修改代码后

        static int you = 0;
        static int me = 0;
        private static void Thread1() {
            me= 2;
            Volatile.Write(ref you,2);
        }
        private static void Thread2()
        {
            if (Volatile.Read(ref you) == 2) {
                Console.WriteLine(me);

        }
        static int you = 0;
        static int me = 0;
        private static void Thread1() {
            me= 2;
            Volatile.Write(ref you,2);
        }
        private static void Thread2()
        {
            if (Volatile.Read(ref you) == 2) {
                Console.WriteLine(me);

        }

这儿因为Volatile.Write使编写翻译器会确认保证函数中,全体在Write方法从前实施的多少读写操作都在Write方法写入在此之前就实施了。

那儿因为Volatile.Write使编译器会确认保证函数中,全数在Write方法在此以前实施的数额读写操作都在Write方法写入从前就举行了。

也等于说编写翻译器不会在实施的时候将you=2放在me=2后边了。解决了在此之前说的第3种情状。

也正是说编写翻译器不会在履行的时候将you=2放在me=2前边了。化解了事先说的率先种意况。

而Volatile.Read保证函数中,全体在Read方法执行之后的数码读写操作,一定实在Read方法执行后才实行。

而Volatile.Read保障函数中,全体在Read方法执行之后的数量读写操作,一定实在Read方法执行后才开始展览。

也正是说me读取肯定在有读取数据的末尾,也就缓解了前头说的第一种景况。

也正是说me读取肯定在有读取数据的末端,也就缓解了前边说的第叁种情景。

而是正如您所看到的,这很难领悟,关键是上下一心用到项目中都会认为真蛋疼,还得百度时而探望是否Read和Write的担保记混了。

而是正如您所观望的,那很难知晓,关键是本身用到品种中都会以为真蛋疼,还得百度时而探视是或不是Read和Write的保管记混了。

由此为了简化编制程序,C#编写翻译器提供了volatile关键字,它能够选择于事先涉嫌的这些原子性的简便类型。

于是为了简化编制程序,C#编写翻译器提供了volatile关键字,它能够动用于事先涉嫌的那么些原子性的简约类型。

volatile证明后,JIT编写翻译器会确认保障易变字段都以以易变读取和写入的法门举办,不必显示调用Read和Write。(也正是说只要用了volatile,那么me=2的效率正是Volatile.Write(ref
me,2),同理读也是同一)

volatile表明后,JIT编写翻译器会确认保障易变字段都是以易变读取和写入的点子展开,不必突显调用Read和Write。(约等于说只要用了volatile,那么me=2的意义正是Volatile.Write(ref
me,2),同理读也是如出一辙)

并且volatile会告诉C#编写翻译器和JIT编写翻译器不将字段缓存到CPU寄存器,确定保证字段的具有读写操作都在内部存款和储蓄器中实行。

并且volatile会告诉C#编写翻译器和JIT编写翻译器不将字段缓存到CPU寄存器,确认保证字段的持有读写操作都在内部存款和储蓄器中开展。

未来再改写此前的代码:

于今再改写从前的代码:

        static volatile int you = 0;
        static int me = 0;
        private static void Thread1() {
            me= 2;
            you=2;
        }
        private static void Thread2()
        {
            if (you == 2) {
                Console.WriteLine(me);

        }
        static volatile int you = 0;
        static int me = 0;
        private static void Thread1() {
            me= 2;
            you=2;
        }
        private static void Thread2()
        {
            if (you == 2) {
                Console.WriteLine(me);

        }

而是小编却表示并不喜欢volatile关键字,因为出现上述所说的景况的票房价值极低,并且volatile禁止优化后对质量会有影响。且C#不扶助以传引用的章程传送volatile变量给有个别函数。

可是作者却代表并不爱好volatile关键字,因为现身上述所说的动静的票房价值十分的低,并且volatile禁止优化后对品质会有震慑。且C#不匡助以传引用的法门传送volatile变量给某些函数。

互锁构造

互锁构造

协和式飞机互锁构造,就要说System.Threading.Interlocked类提供的方法。

情商互锁构造,就要说System.Threading.Interlocked类提供的点子。

以此类中的每种方法都举办二次原子性的读也许写操作。

其一类中的每一种方法都施行三次原子性的读或然写操作。

以此类中的全体办法都创造了整机的内部存款和储蓄器栅栏,也正是说调用有些Interlocked方法以前的此外变量写入都在那一个Interlocked方法调用此前实施,而以此调用之后的别的变量读取都在这几个调用之后读取。

本条类中的全部办法都成立了一体化的内部存款和储蓄器栅栏,也正是说调用有些Interlocked方法此前的别的变量写入都在那个Interlocked方法调用以前实施,而这一个调用之后的别的变量读取都在那几个调用之后读取。

它的功能就相当从前的Volilate的Read和Write的作用加在一起。

它的功能就等于在此之前的Volilate的Read和Write的作用加在一起。

小编推荐使用Interlocked的主意,它们不仅快,而且也能做过多事务,比简单的加(Add),自增(Increment),自减(Decrement),交流(Exchange)。

小编推荐使用Interlocked的方法,它们不仅快,而且也能做过多事情,比简单的加(Add),自增(Increment),自减(Decrement),交流(Exchange)。

Interlocked的主意即便好用,但关键用以操作Int类型。

Interlocked的措施即使好用,但首要用以操作Int类型。

假使想要原子性地操作类对象中的一组字段,那么能够用以下办法达成:

一经想要原子性地操作类对象中的一组字段,那么能够用以下办法达成:

/// <summary>
    /// 简单的自旋锁
    /// </summary>
    struct SimpleSpinLock {
        private Int32 m_ResourceInUse;//0表示false,1表示true

        public void Enter() {
            while (true) {
                //将资源设为正在使用,Exchange方法的意思是,将m_ResourceInUse赋值为1,并返回原来的m_ResourceInUse的值
                if (Interlocked.Exchange(ref m_ResourceInUse, 1) == 0) return;

            }
        }

        public void Leave() {
            Volatile.Write(ref m_ResourceInUse, 0);
        }
    }
    public class SomeResource {
        private SimpleSpinLock m_sl = new SimpleSpinLock();
        public void AccessResource() {
            m_sl.Enter();
            /*每次只有一个线程能访问到这里的代码*/
            m_sl.Leave();
        }
    }
/// <summary>
    /// 简单的自旋锁
    /// </summary>
    struct SimpleSpinLock {
        private Int32 m_ResourceInUse;//0表示false,1表示true

        public void Enter() {
            while (true) {
                //将资源设为正在使用,Exchange方法的意思是,将m_ResourceInUse赋值为1,并返回原来的m_ResourceInUse的值
                if (Interlocked.Exchange(ref m_ResourceInUse, 1) == 0) return;

            }
        }

        public void Leave() {
            Volatile.Write(ref m_ResourceInUse, 0);
        }
    }
    public class SomeResource {
        private SimpleSpinLock m_sl = new SimpleSpinLock();
        public void AccessResource() {
            m_sl.Enter();
            /*每次只有一个线程能访问到这里的代码*/
            m_sl.Leave();
        }
    }

上边的代码原理正是,当一个线程调用Enter后,那么就会return,并置m_ResourceInUse为1,此时意味着能源被占用了。

上面包车型地铁代码原理便是,当三个线程调用Enter后,那么就会return,并置m_ResourceInUse为1,此时意味着财富被占用了。

一经别的二个线程再调用Enter,那么获得的m_ResourceInUse为1,所以不会再次回到,就连发推行循环,直到第三个线程调用Leave函数,将m_ResourceInUse置为0。

借使别的二个线程再调用Enter,那么获得的m_ResourceInUse为1,所以不会回到,就时时刻刻实施循环,直到第一个线程调用Leave函数,将m_ResourceInUse置为0。

规律非常粗略,但相信看那些情势的人也理应很清楚了,约等于说只要第二个线程不脱离,其余具有的线程都要时时刻刻拓展巡回操作(术语为自旋)。

规律很简短,但相信看这些方式的人也应该很明亮了,也正是说只要第多少个线程不脱离,其余具有的线程都要时时刻刻开始展览巡回操作(术语为自旋)。

于是自旋锁应该是用于保养那么些会进行得不行快的代码区域。(且不要用在单CPU机器上,因为占有锁的线程不可能高效释放锁)

故此自旋锁应该是用于保险那多少个会实施得拾叁分快的代码区域。(且毫无用在单CPU机器上,因为占有锁的线程不可能飞快释放锁)

假定占有锁的线程优先级地狱想要获取锁的线程,那么那就造成占有锁的线程也许一直没机会运转,更别提释放锁了。(那便是活锁,前面也涉及了)

一经占有锁的线程优先级鬼世界想要获取锁的线程,那么那就导致占有锁的线程可能根本没机会运营,更别提释放锁了。(那正是活锁,后面也论及了)

实在FCL就提供了三个近似的自旋锁,也正是System.Threading.SpinLock结构,并且照旧用了SpinWait结构来增强质量。

事实上FCL就提供了三个像样的自旋锁,也正是System.Threading.SpinLock结构,并且依旧用了SpinWait结构来狠抓品质。

鉴于SpinLock和在此以前我们本人写的SimpleSpinLock都以结构体,也便是说他们都是值类型,都以轻量级且内部存款和储蓄器友好的。

出于SpinLock和事先大家团结写的SimpleSpinLock都是结构体,也正是说他们都以值类型,都以轻量级且内部存款和储蓄器友好的。

然而并非传递它们的实例,因为值类型会复制,而你将错过全数的同台。

唯独并非传递它们的实例,因为值类型会复制,而你将错过全数的一起。

其实Interlocked.CompareExchange本来就足以不仅仅用于操作整数,还足以用来操作其余原子性的基元类型,他还有1个泛型方法。

骨子里Interlocked.CompareExchange本来就能够不仅仅用于操作整数,还足以用来操作其它原子性的基元类型,他还有3个泛型方法。

它的作用是,相比较第3个参数和第3个参数,假设双方对等,那么将第二个参数的值赋给第②个参数,并赶回第二个参数在此之前的值。

它的功效是,相比较第②个参数和第一个参数,如若双方对等,那么将第一个参数的值赋给第②个参数,并赶回第三个参数在此以前的值。

水源形式组织

根本情势组织

水源格局比用户格局慢,那些是能够预知的,因为线程要从托管代码转为本机用户形式代码,再转为内核形式代码,然后原路再次来到,也就明白怎么慢了。

水源形式比用户情势慢,这一个是能够预言的,因为线程要从托管代码转为本机用户方式代码,再转为内核方式代码,然后原路再次回到,也就询问怎么慢了。

只是此前也介绍过了,内核情势也负有用户情势所不富有的长处:

唯独从前也介绍过了,内核情势也有着用户形式所不抱有的优点:

  • 水源格局的布局检查和测试到二个能源上的竞争,windows会阻塞输掉的线程,使他不会像在此之前介绍的用户情势那样“自旋”(也等于特别不断循环的鬼),那样也就不会一直占着3个CPU了,浪费能源。
  • 基础情势的布局可完成本机和托管线程相互之间的共同
  • 基础方式的协会可共同在平等台机械的两样进程中运作的线程。
  • 基础方式的协会可采纳安全性设置,幸免未经授权的帐户访问它们。
  • 线程可直接不通,直到集合中全体内核方式组织可用,或直到集合中的任何内核形式结构可用
  • 在基础形式的结构上过不去的线程可钦点超时值;钦命时间内访问不到梦想的能源,线程就可防止去阻塞并履行职务。
  • 水源情势的布局检查和测试到1个财富上的竞争,windows会阻塞输掉的线程,使她不会像此前介绍的用户情势那样“自旋”(也正是11分不断循环的鬼),那样也就不会一向占着3个CPU了,浪费财富。
  • 基础情势的组织可达成本机和托管线程相互之间的三只
  • 基本方式的组织可一并在相同台机器的不等进程中运作的线程。
  • 基本格局的组织可应用安全性设置,幸免未经授权的帐户访问它们。
  • 线程可径直不通,直到集合中具备内核形式结构可用,或直到集合中的任何内核方式组织可用
  • 在基本形式的布局上围堵的线程可钦点超时值;内定时间内访问不到希望的资源,线程就足以清除阻塞并执行职责。

事件和信号量是二种基元内核格局线程同步构造,至于互斥体什么的则是在那二者基础上确立而来的。

事件和信号量是二种基元内核情势线程同步构造,至于互斥体什么的则是在那两者基础上建立而来的。

System.Threading命名空间提供了一个空洞基类WaitHandle。那么些简单的类唯一的功用正是包装二个Windows内校对象句柄。(它有一些派生类伊芙ntWaitHandle,AutoReset伊夫nt,马努alReset伊夫nt,Semaphore,Mutex)

System.Threading命名空间提供了2个空洞基类WaitHandle。那一个大概的类唯一的效益便是包裹一个Windows内核查象句柄。(它有局地派生类伊芙ntWaitHandle,AutoReset伊夫nt,马努alReset伊芙nt,Semaphore,Mutex)

WaitHandle基类内部有3个SafeWaitHandle字段,它兼容3个Win32基本对象句柄。

WaitHandle基类内部有1个SafeWaitHandle字段,它包容3个Win32内查对象句柄。

本条字段在布局1个现实的WaitHandle派生类时初始化。

以此字段在协会1个有血有肉的WaitHandle派生类时初步化。

在1个基石情势的结构上调用的各类方法都意味3个完全的内部存款和储蓄器栅栏。(以前也说过了,表示调用那些办法在此以前的其它变量的写入都无法不在此方法前形成,调用那几个主意之后的别样变量的读取都不能不在此办法后成功)。

在一个基石情势的布局上调用的每一个方法都意味三个完好的内部存款和储蓄器栅栏。(以前也说过了,表示调用那些措施在此之前的任何变量的写入都必须在此措施前成功,调用这么些格局之后的其余变量的读取都必须在此方法后形成)。

那个类中的方法就不具体介绍了,基本上那些方法的重要功能吗个正是调用线程等待3个或几个底层基础对象吸收信号。

本条类中的方法就不具体介绍了,基本上那几个方法的根本成效吗个正是调用线程等待1个或五个底层基础对象吸收信号。

只是要留心在伺机四个的艺术(即WaitAll和WiatAny这种)中,传递的基业数组参数,数组最大要素数不能够当先64,否则会13分。

只是要注意在等候多个的主意(即WaitAll和WiatAny那种)中,传递的基本数组参数,数组最大因素数无法跨越64,否则会极度。

要害讲一下三个内核构造,也是事先WaitHandle的七个一直接轨派生类:

关键讲一下八个内核构造,也是前边WaitHandle的八个一直接轨派生类:

  • EventHandle(Event构造)

    • 事件实际就是由基本维护的Boolean变量。为false就卡住,为true就免去阻塞。
    • 有二种事件,即自动重置事件(AutoReset伊夫nt)和手动重置事件(马努alReset伊夫nt)。区别就在于是不是在触发五个线程的封堵后,将事件自动重置为false。
    • 用自行重置事件写个锁示例如下:

        /// <summary>
          /// 简单的阻塞锁
          /// </summary>
          class SimpleWaitLock {
              private readonly AutoResetEvent m_ResourceInUse;
      
              public SimpleWaitLock() {
                  m_ResourceInUse = new AutoResetEvent(true);//初始化事件,表示事件构造可用
              }
      
              public void Enter() {
                  //阻塞内核,直到资源可用
                  m_ResourceInUse.WaitOne();
              }
      
              public void Leave() {
                  //解除当前线程阻塞,让另一个线程访问资源
                  m_ResourceInUse.Set();
              }
              public void Dispose() {
                  m_ResourceInUse.Dispose();
              }
          }
      

      此示例能够和日前的不行自旋锁绝相比较,调用方法同样。

  • Semaphore(Semaphore构造)

    • Semaphore的英文正是信号量,其实是由基本维护的Int32变量。信号量为0时,在信号量上等候的线程阻塞,信号量大于0时接触阻塞。信号量上伺机的线解除阻塞时,信号量自动减1.
    • 如出一辙二个例证来表示,与地点代码对比之后更清楚:(信号量最大值设置为1的话,且释放的时候也只释放2个以来,那么实际上和事件效果同样)

       /// <summary>
          /// 简单的阻塞锁
          /// </summary>
          class SimpleWaitLock {
              private readonly Semaphore m_ResourceInUse;
      
              public SimpleWaitLock(Int32 maxCount) {
                  m_ResourceInUse = new Semaphore(maxCount, maxCount);
              }
      
              public void Enter() {
                  //阻塞内核,直到资源可用
                  m_ResourceInUse.WaitOne();
              }
      
              public void Leave() {
                  //解除当前线程阻塞,让另外2个线程访问资源
                  m_ResourceInUse.Release(2);
              }
              public void Dispose() {
                  m_ResourceInUse.Close();
              }
          }
      
  • Mutex(Mutex构造)

    • Mutex的国语正是互斥体。代表了一个排斥的锁。
    • 互斥体有2个格外的逻辑,Mutex会记录下线程的ID值,要是释放的时候不是以此线程释放的,那么就不会释放掉,并且还会抛极度。
    • 互斥体实际上在保卫安全1个递归计数,二个线程当前怀有1个Mutex,而后该线程再次在Mutex等待,那么此计数就会递增,而线程调用ReleaseMutex会导致递减,只有计数递减为0,那么这么些线程才会消除阻塞。另一个线程才会称呼该Mutex的全体者
    • Mutex对象供给额外的内部存储器来包容这二个记录下来的ID值和计数新闻,并且锁也会变得更慢了。所以众五人幸免用Mutex对象。
    • 平常1个措施在应用叁个锁时调用了另2个艺术,这么些办法也要用到锁,那么就足以考虑用互斥体。因为用事件那种根本构造方法的话,在调用的另三个方法中用到锁就会招致短路,从而死锁。例子:

       public class SomeResource {
              private readonly Mutex m_lock = new Mutex();
              public void Method1() {
                  m_lock.WaitOne();
                  Method2();//递归获取锁
                  m_lock.ReleaseMutex();
              }
              public void Method2()
              {
                  m_lock.WaitOne();
                  /*做点什么*/
                  m_lock.ReleaseMutex();
              }
          }
      

      像上述的那种结构要是不难得用事件来写就会有反常态,然则并不是不可能用事件去递归实现,而且只要用以下的形式递归完结效益反而会更好:

    • 用事件措施完成递归锁:

      /// <summary>
          /// 事件构造实现的递归锁,效率比Mutex高很多
          /// </summary>
          class ComplexWaitLock:IDisposable {
              private  AutoResetEvent m_lock=new AutoResetEvent(true);
              private Int32 m_owningThreadId = 0;
              private Int32 m_lockCount = 0;
      
              public void Enter() {
                  //获取当前线程ID
                  Int32 currentThreadId = Thread.CurrentThread.ManagedThreadId;
                  //当前线程再次进入就会递增计数
                  if (m_owningThreadId == currentThreadId) {
                      m_lockCount++;
                      return;
                  }
                  m_lock.WaitOne();
                  m_owningThreadId = currentThreadId;
                  m_lockCount = 1;
      
              }
      
              public void Leave() {
                  //获取当前线程ID
                  Int32 currentThreadId = Thread.CurrentThread.ManagedThreadId;
                  if (m_owningThreadId != currentThreadId)
                      throw new InvalidOperationException();
      
                  if (--m_lockCount == 0) {
                      m_owningThreadId = 0;
                      m_lock.Set();
                  } 
              }
              public void Dispose() {
                  m_lock.Dispose();
              }
          }
      

      下边包车型客车代码其实很好搞懂,正是用事件把Mutex的玩法自个儿达成了。但是上边的代码之所以比Mutex快,是因为这一个代码都以用托管代码在促成,而不是像Mutex一样用基本代码,仅仅唯有调用事件组织的方法时才会用到根本代码。

  • EventHandle(Event构造)

    • 事件其实正是由基础维护的Boolean变量。为false就打断,为true就排除阻塞。
    • 有三种事件,即自行重置事件(AutoReset伊芙nt)和手动重置事件(马努alReset伊芙nt)。不相同就在于是或不是在触及三个线程的隔阂后,将事件自动重置为false。
    • 用电动重置事件写个锁示例如下:

        /// <summary>
          /// 简单的阻塞锁
          /// </summary>
          class SimpleWaitLock {
              private readonly AutoResetEvent m_ResourceInUse;
      
              public SimpleWaitLock() {
                  m_ResourceInUse = new AutoResetEvent(true);//初始化事件,表示事件构造可用
              }
      
              public void Enter() {
                  //阻塞内核,直到资源可用
                  m_ResourceInUse.WaitOne();
              }
      
              public void Leave() {
                  //解除当前线程阻塞,让另一个线程访问资源
                  m_ResourceInUse.Set();
              }
              public void Dispose() {
                  m_ResourceInUse.Dispose();
              }
          }
      

      此示例能够和前面包车型大巴不得了自旋锁绝相比较,调用方法同样。

  • Semaphore(Semaphore构造)

    • 塞马phore的英文就是信号量,其实是由基本维护的Int32变量。信号量为0时,在信号量上等候的线程阻塞,信号量大于0时接触阻塞。信号量上等候的线解除阻塞时,信号量自动减1.
    • 一如既往二个例证来表示,与地点代码相比较之后更清晰:(信号量最大值设置为1的话,且释放的时候也只释放3个以来,那么实际上和事件效果同样)

       /// <summary>
          /// 简单的阻塞锁
          /// </summary>
          class SimpleWaitLock {
              private readonly Semaphore m_ResourceInUse;
      
              public SimpleWaitLock(Int32 maxCount) {
                  m_ResourceInUse = new Semaphore(maxCount, maxCount);
              }
      
              public void Enter() {
                  //阻塞内核,直到资源可用
                  m_ResourceInUse.WaitOne();
              }
      
              public void Leave() {
                  //解除当前线程阻塞,让另外2个线程访问资源
                  m_ResourceInUse.Release(2);
              }
              public void Dispose() {
                  m_ResourceInUse.Close();
              }
          }
      
  • Mutex(Mutex构造)

    • Mutex的中文正是互斥体。代表了1个排斥的锁。
    • 互斥体有3个额外的逻辑,Mutex会记录下线程的ID值,借使释放的时候不是以此线程释放的,那么就不会释放掉,并且还会抛十分。
    • 互斥体实际上在珍重叁个递归计数,三个线程当前持有一个Mutex,而后该线程再度在Mutex等待,那么此计数就会递增,而线程调用ReleaseMutex会促成递减,唯有计数递减为0,那么那几个线程才会消除阻塞。另三个线程才会称呼该Mutex的全部者
    • Mutex对象急需额外的内部存款和储蓄器来包容这个记录下来的ID值和计数音讯,并且锁也会变得更慢了。所以众两个人幸免用Mutex对象。
    • 万般三个方法在使用二个锁时调用了另二个办法,这么些法子也要用到锁,那么就能够设想用互斥体。因为用事件那种基础构造方法的话,在调用的另3个艺术中用到锁就会导致短路,从而死锁。例子:

       public class SomeResource {
              private readonly Mutex m_lock = new Mutex();
              public void Method1() {
                  m_lock.WaitOne();
                  Method2();//递归获取锁
                  m_lock.ReleaseMutex();
              }
              public void Method2()
              {
                  m_lock.WaitOne();
                  /*做点什么*/
                  m_lock.ReleaseMutex();
              }
          }
      

      像上述的那种结构假设简单得用事件来写就会有标题,但是并不是无法用事件去递归达成,而且只要用以下的不二法门递归实现效益反而会更好:

    • 用事件措施贯彻递归锁:

      /// <summary>
          /// 事件构造实现的递归锁,效率比Mutex高很多
          /// </summary>
          class ComplexWaitLock:IDisposable {
              private  AutoResetEvent m_lock=new AutoResetEvent(true);
              private Int32 m_owningThreadId = 0;
              private Int32 m_lockCount = 0;
      
              public void Enter() {
                  //获取当前线程ID
                  Int32 currentThreadId = Thread.CurrentThread.ManagedThreadId;
                  //当前线程再次进入就会递增计数
                  if (m_owningThreadId == currentThreadId) {
                      m_lockCount++;
                      return;
                  }
                  m_lock.WaitOne();
                  m_owningThreadId = currentThreadId;
                  m_lockCount = 1;
      
              }
      
              public void Leave() {
                  //获取当前线程ID
                  Int32 currentThreadId = Thread.CurrentThread.ManagedThreadId;
                  if (m_owningThreadId != currentThreadId)
                      throw new InvalidOperationException();
      
                  if (--m_lockCount == 0) {
                      m_owningThreadId = 0;
                      m_lock.Set();
                  } 
              }
              public void Dispose() {
                  m_lock.Dispose();
              }
          }
      

      地点的代码其实很好搞懂,便是用事件把Mutex的玩法本人达成了。但是下边包车型大巴代码之所以比Mutex快,是因为这一个代码都以用托管代码在落到实处,而不是像Mutex一样用基本代码,仅仅唯有调用事件组织的方式时才会用到根本代码。


相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图