本章介绍了一些轻量级的同步原语,其中有很大部分是.NET Framework 4才引入的。
System.Threading.Barrier
用于一段程序分成多个阶段,每个阶段的开始都需要之前的阶段完成。如果这段程序需要并行化。可以在每段之间采用Barrier。
还可以设置在每个阶段之间的动作。
task在Barrier中成为参与者(participant),在构造的时候要设定数量,也可以动态的增删。
异常和超时的处理可以参考代码。
相比于使用使用Task的ContinueWith方法实现多个阶段的串行,Barrier可以减少非常多的Task数量。
用完后需要Dispose
Task[] _tasks; Barrier _barrier; _tasks = new Task[ 4]; _barrier = new Barrier( 4, (barrier) = > { Console.WriteLine( "Current phase: {0}", barrier.CurrentPhaseNumber); }); for ( int i = 0; i < 4; i ++) { _tasks[i] = Task.Factory.StartNew((num) = > { //...阶段1 if ( !_barrier.SignalAndWait(TIMEOUT)) { //... } //...阶段2 try { _barrier.SignalAndWait(); } catch (BarrierPostPhaseException bppex) { //.. break; } //...阶段3 _barrier.SignalAndWait(); }, i); }
互斥锁
C#提供了lock关键字来获取一个互斥锁。lock块编译时会被替换成 System.Threading.Monitor的使用。
需要注意的点有
- lock和Monitor只能锁引用类型的实例,不要对值类型使用lock或Monitor。
- 要避免锁定我iabuduixinag,避免跨成员或类的边界获得或释放锁。
- 临界区中的代码应该尽量保持简单。
lock (_obj) { //... } //编译时lock块会被替换成如下 bool lockTaken = false; Monitor.Enter(_obj, ref lockTaken); try { //... } finally { if (lockTaken) { Monitor.Exit(_obj); } }
如果是直接使用Monitor还可以使用TryEnter来设置超时
bool lockTaken = false; try { Monitor.TryEnter(_obj, 2000, ref lockTaken); if ( !lockTaken) { throw new TimeoutException(...); } //... } finally { if (lockTaken) { Monitor.Exit(_obj); } }
自旋
Monitor开销非常大。如果锁的时间非常短,自旋锁能获取更好的性能。
但是如果长时间的自旋,SpinWait会让出时间片,并触发上下文切换。这和忙等不同。
如果多个任务都需要自旋锁,那么每一个任务都应该使用自己的实例
SpinWait在单核没有实际意义,因为必然是要做上下文切换才有可能等到的。
SpinWait.SpinUntil(Func<bool> condition,int millisecondsTimeout)提供了基于自旋的等待发方案
var sl = new SpinLock( false); bool lockTaken = false; try { sl.TryEnter( 2000, ref lockTaken); if ( !lockTaken) { throw new TimeoutException(...); } //.... } finally { if (lockTaken) { sl.Exit( false); } }
System.Threading.ManualResetEventSlim
ManualResetEventSlim是ManualResetEvent的简化版。不能跨进程或跨AppDomain。
ManualResetEventSlim是一个带有两个可能状态的事件对象,设置信号(true)和取消信号(false)
利用这个可以进行通讯
使用完后需要Dispose。
private ManualResetEventSlim manualResetEvent1; private ManualResetEventSlim manualResetEvent2; //method1 try { manualResetEvent1.Set(); //.. } finally { manualResetEvent1.Reset(); } //method2 try { manualResetEvent2.Set(); if ( !manualResetEvent1.Wait(TIMEOUT)) { throw new TimeoutException(...); } //... } finally { // Switch to unsignaled/unset manualResetEvent2.Reset(); }
System.Threading.SemaphoreSlim
System.Threading.Semaphore的轻量级版本。不能跨进程或跨AppDomain。
提供一个信号量机制来限制资源的并发访问。
用完之后需要Dispose。
SemaphoreSlim _semaphore; _semaphore.Wait(); try { //... } finally { _semaphore.Release(); }
System.Threading.CountdownEvent
CountdownEvent带有一个初始计数器,可以发出一个信号,令计数减一。调用Wait方法时会被阻塞直到计数器达到0。
用完后需要Dispose
private static CountdownEvent _countdown; //Main thread _countdown = new CountdownEvent(MIN_PATHS); //... try { //new Task //... } finally { _countdown.Dispose(); } //Task1 try { //... } finally { _countdown.Signal(); } //Task2 _countdown.Wait(); //...
原子操作
在并行的环境下对共享变量的一些最基本的操作都是不安全的。
把共享变量的操作都加锁,代价又太大。
System.Threading.Interlocked,为多线程的共享变量提供原子操作。
包括 递增 递减 加法 赋值 比较 读 等等。
int total = 0; Interlocked.Increment( ref total);