• 《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位系统不需要,直接访问就可以了。





  • 相关阅读:
    小程序查看导航
    PHP计算两个坐标之间的距离
    微信小程序获取位置
    小程序重置index,重置item
    nmap使用教程
    boost checked_delete提升安全性
    转: 带你玩转Visual Studio——带你理解多字节编码与Unicode码
    visual studio 开发linux程序
    stl 比较和boost LessThanComparable
    c++11 auto unique_ptr 等
  • 原文地址:https://www.cnblogs.com/atskyline/p/3236532.html
Copyright © 2020-2023  润新知