• 多线程操作


    有CSDN的朋友问一个问题,“Lock关键字不是有获取锁、释放锁的功能吗?...为什么还需要执行Pulse?”
    也有朋友有些疑点,“用lock就不要用monitor了”,“Monitor.Wait完全没必要”,“为什么Pulse和Wait方法必须从同步的代码块内调用?”

    这些疑问很自然。在大部分情况下,lock确实能基本达到我们要求资源同步的目的,加上配合其他同步工具,比如事件(AutoResetEvent)等的应用,日常工作中确实没有太多机会需要用到Monitor.Wait和Pulse。不过,虽然较少机会用到,事实上Wait和Pulse跟lock完全不是一回事。他们提供了更细腻的同步功能,能达到lock作不来的功能。

    为更好的回答和解释这些疑问,该帖将首先介绍Wait和Pulse的用途,通过一个简单例子逐条分析同步的过程;然后提供一个用轻量级的lock,Wait和Pulse来实现一个事件通知的实例;最后谈谈DotNet4对lock编译展开的一点有趣变化。

    让我们首先看看MSDN对Monitor.Wait的解释(链接见注释):
    释放对象上的锁并阻止当前线程,直到它重新获取该锁。...

    该解释的确很粗糙,很难理解。让我们来看看它下面的备注:
    同步的对象包含若干引用,其中包括对当前拥有锁的线程的引用、对就绪队列的引用和对等待队列的引用。

    这个多少还给了点东西,现在我们脑海中想像这么一幅图画:

    Assembly code
     
    ?
    1
    2
    3
             |- 拥有锁的线程
    lockObj->|- 就绪队列(ready queue)
             |- 等待队列(wait queue)

    当一个线程尝试着lock一个同步对象的时候,该线程就在就绪队列中排队。一旦没人拥有该同步对象,就绪队列中的线程就可以占有该同步对象。这也是我们平时最经常用的lock方法。
    为了其他的同步目的,占有同步对象的线程也可以暂时放弃同步对象,并把自己流放到等待队列中去。这就是Monitor.Wait。由于该线程放弃了同步对象,其他在就绪队列的排队者就可以进而拥有同步对象。
    比起就绪队列来说,在等待队列中排队的线程更像是二等公民:他们不能自动得到同步对象,甚至不能自动升舱到就绪队列。而Monitor.Pulse的作用就是开一次门,使得一个正在等待队列中的线程升舱到就绪队列;相应的Monitor.PulseAll则打开门放所有等待队列中的线程到就绪队列。

    比如下面的程序:

    class Program
    {
        static void Main(string[] args)
        {
            new Thread(A).Start();
            new Thread(B).Start();
            new Thread(C).Start();
            Console.ReadLine();
        }
    
        static object lockObj = new object();
        static void A()
        {
            lock (lockObj)               //进入就绪队列
            {
                Thread.Sleep(1000);
                Monitor.Pulse(lockObj);
                Monitor.Wait(lockObj);   //自我流放到等待队列
            }
            Console.WriteLine("A exit...");
        }
        static void B()
        {
            Thread.Sleep(500);
            lock (lockObj)               //进入就绪队列
            {
                Monitor.Pulse(lockObj);
            }
            Console.WriteLine("B exit...");
        } 
        static void C()
        {
            Thread.Sleep(800);
            lock (lockObj)               //进入就绪队列
            {
            }
            Console.WriteLine("C exit...");
        }
    }
    T  线程A
    0  lock( lockObj )
    1  {
    2     //...           线程B                   线程C
    3     //...           lock( lockObj )       lock( lockObj )
    4     //...           {                     {
    5     //...              //...
    6     //...              //...
    7     Monitor.Pulse      //...
    8     Monitor.Wait       //...
    9     //...              Monitor.Pulse
    10    //...           }                     }
    11 }
    
    时间点0,假设线程A先得到了同步对象,它就登记到同步对象lockObj的“拥有者引用”中。
    时间点3,线程B和C要求拥有同步对象,他们将在“就绪队列”排队:
                |--(拥有锁的线程) A
                |
    3  lockObj--|--(就绪队列)   B,C
                |
                |--(等待队列)
    
    时间点7,线程A用Pulse发出信号,允许第一个正在"等待队列"中的线程进入到”就绪队列“。但由于就绪队列是空的,什么事也没有发生。
    时间点8,线程A用Wait放弃同步对象,并把自己放入"等待队列"。B,C已经在就绪队列中,因此其中的一个得以获得同步对象(假定是B)。B成了同步
    
    对象的拥有者。C现在还是候补委员,可以自动获得空缺。而A则被关在门外,不能自动获得空缺。
                |--(拥有锁的线程) B
                |
    8  lockObj--|--(就绪队列)   C
                |
                |--(等待队列)   A
    
    时间点9,线程B用Pulse发出信号开门,第一个被关在门外的A被允许放入到就绪队列,现在C和A都成了候补委员,一旦同步对象空闲,都有机会得它。
                |--(拥有锁的线程) B
                |
    9  lockObj--|--(就绪队列)   C,A
                |
                |--(等待队列)
    
    
    时间点10,线程B退出Lock区块,同步对象闲置,就绪队列队列中的C或A就可以转正为拥有者(假设C得到了同步对象)。
                |--(拥有锁的线程) C
                |
    10 lockObj--|--(就绪队列)   A
                |
                |--(等待队列)
    
    随后C也退出Lock区块,同步对象闲置,A就重新得到了同步对象,并从Monitor.Wait中返回...
    最终的执行结果就是:
    B exit...
    C exit...
    A exit...

    参考地址:http://bbs.csdn.net/topics/380095508

    深入浅出多线程系列之一

     http://www.cnblogs.com/LoveJenny/archive/2011/05/21/2049300.html

    5天不再惧怕多线程——第二天 锁机制

    http://www.cnblogs.com/huangxincheng/archive/2012/03/14/2397068.html

  • 相关阅读:
    113.dynamic_cast 虚函数 通过子类初始化的父类转化为子类类型
    112.虚函数强化
    111.final与override
    110.纯虚函数
    109.虚函数与析构构造
    custom-ubuntu-server-iso
    定制ubuntu的时候修改proseed
    centos使用U盘做启动盘
    fio的配置使用
    持续运行一个命令-并且将结果输出到文本
  • 原文地址:https://www.cnblogs.com/cpugege/p/4248043.html
Copyright © 2020-2023  润新知