多线程并发同步总结

前段做Zookeeper的Client4Net,涉及多线程较多。完了对多线程同步做下总结。由于是简单的汇总,主要给自己复习用,代码就不贴出来了。

1.锁

  Lock:

    这个大家都知道没什么可以说的,内部使用Monitor实现,等同于try { Monitor.Enter(obj,ref lockTaKen); }finally { Monitor.Exit(obj); } 。

    注意几点就好:

      Lock(this)锁定当前对象,不建议;

      Lock(type)锁定某类型,这个范围太大,不小心就是死锁;效率低,不建议;

      Lock(string)这个比较疯狂,鉴于string的特殊性,所有Equal的string都会被锁住;没特殊要求不要使用;

      Lock(obj): 推荐,声明 static volatile object作为LockToken;

  Monitor:  

    静态类,作用范围全局。需要显式的Enter/TryEnter和Exist。比较简单不要将值类型当作参数传入就好,否则装箱后就变成锁定不同的对象了。

    需要注意一点是Pulse/TryPulse和Wait相关的就绪(ready)队列等待(waiting)队列,关于这个MSDN解释的比较模糊(就绪队列:包含准备获取锁的线程;等待队列:包含等待对象状态更改通知的线程)。

    首先只有持有锁的线程才能调用Wait和Pulse,一旦Wait必须Pulse才能被唤醒,Wait后释放锁,并进入等待队列。最大的好处是Wait的线程一旦收到Pulse信号就会从等待队列中插队到就绪队列以使其能尽可能早的获取锁。最好使用其包含超时参数的重载,这样超时后会马上进入就绪队列并能根据其返回值决定是否再次Wait,也避免后续获取锁的线程在Pluse前Exit造成无限等待。

      

  Mutex:

    继承自WaitHandle, 静态方法全局可跨进程,实例化方法作用与当前域;

    主要API为继承自WaitHandle的WaitOne: 等待waitHandle信号。堵塞当前线程,超过预期等待时间后返回False。信号等同MenuResetHandle、AutoResetHandle可用于互操作。这个用起来太危险了,跨进程锁定没有特殊理由建议换个方式实现进程同步。碰到过一次,Trouble Shooting过程生不如死。不多说。最后重现将内存全转储到文件一点点找才发现。

  SpinLock: 自旋锁,这货是值类型,意味这更轻量级、更高效、大量使用也会更耗资源。(注意区分SpinWait)等待时通过自旋不退出CPU执行,不用切换效率高,粒度较细,如锁定时间较长将消耗CPU,非特殊原因不推荐使用。建议其他锁类型存在性能问题后再来试试这个。 

  ReadWriteLock/ReadWriteLockSlim: 读写锁,4.5推荐后者。读锁,写锁,可升级读锁;我写别人都不能读,任何人读时都不能写。类似与数据库的可重复读隔离级别。

    Semaphore: 继承者WaitHandle,信号量,创建指定资源数的锁,可用以实现类似池的结构。初始化时指定最大资源数,资源数为零是堵塞新的资源申请线程,等待占用资源线程推出占用。和CountDownEvent相反。主要API为WaitOne 继承自WaitHandle,进入,不必多说。注意这个继承自WaitHandle,类本身没有继承自WaitHandle,没看源码,应该内嵌WaitHandle实例。所以依然放Lock没有放到通知里。Release: 退出,-1/-N。

2.通知 

  WaitHandle

    应该算同步信号量最重要的一个基类(抽象类),提供Wait虚方法接口供子类实现了。同时提供WaitAll,WaitAny,SignalAndWait的静态方法用以调度协调跨子类的信号量。

  Join : Thread方法,阻塞当前线程知道指定时间或某个线程终止。不用多说。

  EventWaitHandle : 另一个重量级的类,继承自WaitHandle,通过设置EventHandleMode枚举设置手动或自动释放, 更常使用子类AutoResetEvent,ManualResetEvent。

    主要API:

      SignalAndWait: 静态,根据属性EventHandleMode设置信号类型,AutoRest: 调用一次发出一个释放信号,使一个调用该类实例的Set方法阻塞的线程继续,然后信号重置。

      MenuRest:信号不会自动重置,调用后所有调用该类实例的Set方法阻塞的线程继续。

      Set : 将事件状态设置为终止状态,允许一个或多个等待线程继续。某线程:“我暂停了哈,可以走了通知我”。

      Reset : 将事件状态设置为非终止状态,导致线程阻止。handle广播:“你们可以走了”。(AutoRest:“走一个”;MenuRest:“都走吧”)。

      WaitOne: 等待指定时间获取信号量,没等到返回False继续;指定时间内收到信号继续。类似与:if(ewh.WaitOne(1000)){//超时前获得信号}else{//超时没获得信号}。

  AutoRestEvent/AutoRestEventSlim: 继承自EventWaitHandle,EventHandleMode = AutoRest的EventHandleMode。后者更轻量级,推荐后者。   

  MenualRestEvent/MenualRestEventSlim: 继承自EventWaitHandle,EventHandleMode = MenualRestEvent的EventHandleMode。后者更轻量级,推荐后者。

    Barrier: 屏障,可是线程分阶段执行,配合Parallel使用可以完成这用的功能:第一阶段:3个独立任务,都完成进入下已阶段;下一阶段可以是1个或多个任务并行。构造函数可为一个Int和一个Action的参数,当SinalandWait的次数达到Int参数值时执行Action。Like this:

    Barrier br = new(2,(b)=>{//do something;}); 
      Action a = ()=>{//do 1; br.SinalandWait(); //do 2; br.SinalandWait(); //do 3};
      Action b = ()=>{//do 1; br.SinalandWait(); //do 2; br.SinalandWait(); //do 3}; 
      Action c = ()=>{//do 1; br.SinalandWait(); //do 2; br.SinalandWait(); //do 3};  
      Action d = ()=>{//do 1; br.SinalandWait(); //do 2; br.SinalandWait(); //do 3};   
  Parallel.Invoke(a,b,c);
//Parallel.Invoke(a,b,c,d)//will throw exception;

    主要API:

      AddParticipant/AddParticipants: 添加一个/多个参与者(任务执行者,Action),这个接口怪怪的。不理解参数都是Int为啥设计成这样。

      RemoveParticipant/RemoveParticipants :移除一个/多个参与者。

      SinalAndWait: 线程告诉Barrier:“我到了啊,等在等着呢,什么时候继续喊我,我还有其他事要接着干呢”,Barrier:“好,都到齐了我会喊你继续的。”。

  SpinWait: 轻量级信号量,通过自旋等待信号,不同退出CPU执行。

    主要API:

      Reset: 重载自旋计数器,重新开始计数。

      Spinone: 自旋一次,通常在循环里使用,自旋一次后检查条件是否满足。

      Count:已自旋次数。

      SpinUnit:最重要函数,包含一个Func参数和一个时间,自旋一次后执行返回Func结果。返回False继续自旋,直到超时推出。     

3. 连锁

  InterLocked:原子操作类。提供加减、替换、递增、递减等原子操作。比较简单没什么可说的。

      

  

原文地址:https://www.cnblogs.com/bibiKu/p/2987308.html