《C#并行编程高级教程》第5章 协调数据结构 笔记

本章介绍了一些轻量级的同步原语,其中有很大部分是.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);
其中需要注意的一点,如果在32位系统下。对64位数值的读取不是原子操作。需要使用Interlocked.Read(ref long location)。64位系统不需要,直接访问就可以了。





转载于:https://www.cnblogs.com/atskyline/p/3236532.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值