zoukankan      html  css  js  c++  java
  • C# 多线程系列(六)

    同步

      当多个线程共享一些数据的时候,我们就需要使用同步技术,确保一次只有一个线程访问合改变共享状态。注意,同步问题与争用和死锁有关。

    例:

    static int idx = 0;
    static void Add()
    {
        for (int i = 0; i < 50000; i++)
        {
            idx++;
        }
    }
    static void Main()
    {
        const int SIZE = 40;
        Task[] arr = new Task[SIZE];
        while (true)
        {
            for (int i = 0; i < SIZE; i++)
            {
                arr[i] = new Task(Add);
                arr[i].Start();         //启动多个线程
            }
    
            for (int i = 0; i < SIZE; i++)
            {
                arr[i].Wait();          //等待线程完成
            }
    
            Console.WriteLine(idx);
            Thread.Sleep(500);
            idx = 0;//  重置数据,再次运行
        }
    }

    结果:

    1717634
    1652989
    1444839
    1272385
    1558097
    1297459
    1968232
    2000000

    显然,不是我们想要的,我们期望每次运行的结果都是2000000。这是因为idx++不是线程安全的,它的操作包括从内存中获取一个值,给该值递增1,再将它存回内存。这些操作都可能会被线程调度器打断。

    这种情况下,我们就需要一些同步方法解决该问题。

    • lock关键字将语句块标记为临界区,方法是获取给定对象的互斥锁,执行语句,然后释放该锁。在块的开始处调用 Enter,而在块的结尾处调用 Exit。这样可确保当一个线程位于代码的关键部分时,另一个线程不会进入该关键部分。 如果其他线程尝试进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。一个线程,当阻塞的时候,不占用CPU资源。
    static object locker = new object();
    static void Add()
    {
        for (int i = 0; i < 50000; i++)
        {
            lock (locker)
                idx++;
        }
    }
    • Interlocked类用于使变量的简单语句原子话(最小执行单元,不会被中途打断),提供了以线程安全的方式递增、递减、交换和读取值的方法。
    对上例而言,把idx++替换成Interlocked.Increment(ref idx);
    •  Monitor类算是实现锁机制的纯正类,lock语句由编译器解析为使用Monitor类。
    lock(obj)
    {
        //synchronized region for obj
    }
    
    相当于
    
    Monitor.Enter(obj);
    try
    {
        //synchornized region for obj
    }
    finally
    {
        Monitor.Exit(obj)
    }

    用TryEnter可以添加timeout

     1 object obj = new object();
     2 Task.Run(()=>{
     3     lock(obj)
     4     {
     5         Console.WriteLine("lock obj");
     6         Thread.Sleep(3000);
     7     }
     8 });
     9 bool b = Monitor.TryEnter(obj, 2000);
    10 if (b)
    11 {
    12     try
    13     {
    14         Console.WriteLine("monitor enter.");
    15     }
    16     finally
    17     {
    18         Monitor.Exit(obj);
    19     }
    20 }
    21 else
    22 {
    23     Console.WriteLine("monitor enter false.");
    24 }
    25 
    26 Console.ReadKey();

    另外,Monitor还提供了Wait方法,用于释放对象上的锁并阻止当前线程,直到它重新获取该锁。

    提供了Pulse方法用于通知等待队列中的线程锁定对象状态的更改;PulseAll通知所有的等待线程对象状态的更改。

    •  SpinLock自旋锁,如果基于对象锁定(Monitor)的系统开销由于垃圾回收而过高,就可以使用SpinLock结构。如果有大量的锁定(例如,列表中的每个节点都有一个锁定),且锁定的时间总是非常短,SpinLock结构就很有用。应避免使用多个SpinLock结构,也不要调用任何可能阻塞的内容。SpinLock 应仅用于您,这样做可以改进应用程序的性能确定后。 还有一点需要注意 SpinLock 是值类型,为了提高性能。 出于此原因,您必须非常小心,以免意外复制 SpinLock 实例,因为两个实例 (原始项和副本) 都将完全相互独立的这可能会导致错误行为的应用程序。 如果 SpinLock 必须围绕传递实例,则应通过引用而不是通过值传递。

      请不要在存储 SpinLock 只读字段中的实例。

  • 相关阅读:
    网易严选的wkwebview测试之路
    【工程实践】服务器数据解析
    从加班论客户端开发中的建模
    UVaLive 6802 Turtle Graphics (水题,模拟)
    UVaLive 6694 Toy Boxes (二分+想法)
    UVaLive 6693 Flow Game (计算几何,线段相交)
    UVaLive 6698 Sightseeing Bus Drivers (水题,贪心)
    UVaLive 6697 Homework Evaluation (DP)
    UVALive 6692 Lucky Number (思路 + 枚举)
    CodeForces 710E Generate a String (DP)
  • 原文地址:https://www.cnblogs.com/wrbxdj/p/8554876.html
Copyright © 2011-2022 走看看