.NET多线程(八)同步构造

本节主要内容

** 同步构造,基元构造,用户模式构造,易变构造 **
** volatile CLR 保证以下类型的读写操作是原子操作 **
** System.Threading.Monitor **


Boolean,Char,(S)Byte,(U)Int16,(U)Int32,(U)IntPtr,Single,and reference types
注意,这里64位,比如int64,Double不是原子操作。
虽然CLR保证上面类型的操作是原子操作,但是CLR并没有保证读写操作来自编译器,或者CPU优化。
C#编译器,JIT编译器,CPU都可能优化你的代码。


多个线程,访问共享资源,需要线程同步。

解决方案:

(1)首先考虑,调整设计,避免使用共享资源
(2)其次,考虑原子操作,好处是,没有等待,没有死锁
(3)当代码比较复杂,前面2个都不能解决时,我们需要用到同步构造
一种同步构造,是让等待的线程,进入阻塞状态,使用最少的CPU,需要上下文切换,这种就是内核模式同步构造,适合等待时间较长的操作。
.NET多线程,并发同步,同步构造,基元构造,用户模式构造,易变构造,volatile

另一种同步构造,就是等待,这种就需要使用CPU,没有上下文切换,适合非常短时的等待,性能较好,这也称为用户模式同步构造。

第三种,就是混合模式同步构造了,首先使用用户模式同步构造,如果操作时间长了,就使用内核模式同步构造。


优化器,通常只保证单线程,实现你的代码意图(注意,并不一定按你代码写的那样去实现你的意图)
所以,如果是多线程,那么优化后的代码,则可能有潜在的问题。

class Program
{
    private static bool stopFlag = false;

    static void Main(string[] args)
    {
        Thread thread = new Thread(Worker);
        thread.Start();

        Console.WriteLine("主线程等1秒");
        Thread.Sleep(1 * 1000);

        Console.WriteLine("主线程通知工作线程结束");
        stopFlag = true;

        thread.Join();
    }

    private static void Worker(Object o)
    {
        int x = 0;
        while (!stopFlag)
        {
            x++;
        }
        Console.WriteLine("工作线程结束:x = {0}", x);
    }
}

上面这段代码,在 Debug 模式,应该没有问题,在 Release模式,则可能有问题。
问题可能是,Worker 的 while 会死循环。

编译器会优化stopFlag,编译器发现在线程 Worker 方法中,stopFlag没有被改变。
所以编译器,会首先判断stopFlag,如果为flase,那 x = 0
如果为 true,则无限递增 x,这样只会判断1次stopFlag

主线程的stopFlag = true 则可能是在 worker 优化之后,才编译的
具体,这些都是编译器的事情。
我们只需要知道存在,编译器优化,优化可能带来问题。

解决死循环这个问题:加 volatile

private volatile static bool stopFlag = false;

(2)System.Threading.Monitor

(1)混合模式同步构造
(2)互斥,自旋,线程所有权,迭代

(1)同步多个字段,属性,项目
(2)当前线程已经获取锁的情况,再调用 Monitor.Enter 无需再等待获取锁
.NET多线程(六)并发同步,同步构造 Monitor

(1)System.Threading.Monitor 的问题

lock ,自动 try/catch Monitor ,在 try 中出错了怎么办,try 中数据的状态是正确的? 后续线程会使用这些数据?

使用示例

private static readonly object obj = new object();
static void Main(string[] args)
{
    int tCount = 1000;
    int result = 0;
    List taskList = new List();
    for (int i = 0; i < tCount; i++)
    {
        Task task = Task.Factory.StartNew(() =>
        {
            for (int j = 0; j < 1000; j++)
            {
                bool taken = false;
                try // try/finally 保证锁释放
                {
                    Monitor.Enter(obj, ref taken);
                    result = result + 1;
                }
                finally // Enter异常 taken = False
                {
                    if (taken) { Monitor.Exit(obj); }
                }
            }
        });
        taskList.Add(task);
    }
    Task.WaitAll(taskList.ToArray());
    Console.WriteLine(result);
    Console.ReadLine();
}

lock 是 Monitor 上面代码的,语法糖写法
lock 不支持获取锁超时

lock (obj)
{
    result = result + 1;
}

使用 Monitor.TryEnter 判断获取锁是否超时

使用 Monitor 传递信号

Monitor.Wait 表示当前线程放弃锁,进入等待状态
Monitor.Pulse 告诉1个wait线程,锁状态更改
Monitor.PulseAll 告诉所有wait线程,锁状态更改

你可能感兴趣的:(.NET多线程(八)同步构造)