多线程:C#线程同步lock,Monitor,Mutex,同步事件和等待句柄(上)

转自 http://www.cnblogs.com/freshman0216/archive/2008/07/27/1252253.html

本篇从Monitor,Mutex,ManualResetEvent,AutoResetEvent,WaitHandler的类关系图开 始,希望通过本篇的介绍能对常见的线程同步方法有一个整体的认识,而对每种方式的使用细节,适用场合不会过多解释。让我们来看看这几个类的关系图:

1.lock关键字

lock是C#关键词,它将语句块标记为临界区,确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。方法是获取给定对象的互斥锁,执行语句,然后释放该锁。

MSDN上给出了使用lock时的注意事项通常,应避免锁定 public 类型,否则实例将超出代码的控制范围。常见的结构 lock (this)、lock (typeof (MyType)) 和 lock ("myLock") 违反此准则。

1)如果实例可以被公共访问,将出现 lock (this) 问题。

2)如果 MyType 可以被公共访问,将出现 lock (typeof (MyType)) 问题由于一个类的所有实例都只有一个类型对象(该对象是typeof的返回结果),锁定它,就锁定了该对象的所有实例。微软现在建议不要使用 lock(typeof(MyType)),因为锁定类型对象是个很缓慢的过程,并且类中的其他线程、甚至在同一个应用程序域中运行的其他程序都可以访问 该类型对象,因此,它们就有可能代替您锁定类型对象,完全阻止您的执行,从而导致你自己的代码的挂起。

3)由于进程中使用同一字符串的任何其他代码将共享同一个锁,所以出现 lock(“myLock”) 问题。这个问题和.NET Framework创建字符串的机制有关系,如果两个string变量值都是"myLock",在内存中会指向同一字符串对象。

最佳做法是定义 private 对象来锁定, 或 private static对象变量来保护所有实例所共有的数据。

我们再来通过IL Dasm看看lock关键字的本质,下面是一段简单的测试代码:

    lock (lockobject)
    {
        int i = 5;
    }

用IL Dasm打开编译后的文件,上面的语句块生成的IL代码为:

      IL_0045:  call       void [mscorlib]System.Threading.Monitor::Enter(object)
      IL_004a:  nop
      .try
      {
        IL_004b:  nop
        IL_004c:  ldc.i4.5
        IL_004d:  stloc.1
        IL_004e:  nop
        IL_004f:  leave.s    IL_0059
      }  // end .try
      finally
      {
        IL_0051:  ldloc.3
        IL_0052:  call       void [mscorlib]System.Threading.Monitor::Exit(object)
        IL_0057:  nop
        IL_0058:  endfinally
      }  // end handler

通过上面的代码我们很清楚的看到:lock关键字其实就是对Monitor类的Enter()和Exit()方法的封装,并通过try...catch...finally语句块确保在lock语句块结束后执行Monitor.Exit()方法,释放互斥锁。

2.Monitor类

Monitor类通过向单个线程授予对象锁来控制对对象的访问。对象锁提供限制访问临界区的能力。当一个线程拥有对象的锁时,其他任何 线程都不能获取该锁。还可以使用 Monitor 来确保不会允许其他任何线程访问正在由锁的所有者执行的应用程序代码节,除非另一个线程正在使用其他的锁定对象执行该代码。

通过对lock关键字的分析我们知道,lock就是对Monitor的Enter和Exit的一个封装,而且使用起来更简洁,因此Monitor类的Enter()和Exit()方法的组合使用可以用lock关键字替代。

另外Monitor类还有几个常用的方法:

TryEnter()能够有效的解决长期死等的问题,如果在一个并发经常发生,而且持续时间长的环境中使用TryEnter,可以有效防止死锁或者长时间 的等待。比如我们可以设置一个等待时间bool gotLock = Monitor.TryEnter(myobject,1000),让当前线程在等待1000秒后根据返回的bool值来决定是否继续下面的操作。

Wait()释放对象上的锁以便允许其他线程锁定和访问该对象。在其他线程访问对象时,调用线程将等待。脉冲信号用于通知等待线程有关对象状态的更改。

Pulse(),PulseAll()向一个或多个等待线程发送信号。该信号通知等待线程锁定对象的状态已更改,并且锁的所有者准备释放该锁。等待线程被 放置在对象的就绪队列中以便它可以最后接收对象锁。一旦线程拥有了锁,它就可以检查对象的新状态以查看是否达到所需状态。

注意:Pulse、PulseAll和Wait方法必须从同步的代码块内调用。

我们假定一种情景:妈妈做蛋糕,小孩有点馋,妈妈每做好一块就要吃掉,妈妈做好一块后,告诉小孩蛋糕已经做好了。下面的例子用Monitor类的Wait和Pulse方法模拟小孩吃蛋糕的情景。


    //仅仅是说明Wait和Pulse/PulseAll的例子
    //逻辑上并不严密,使用场景也并不一定合适
    class MonitorSample
    {
        private int n = 1;  //生产者和消费者共同处理的数据
        private int max = 10000;

private object monitor = new object();

public void Produce()
        {
            lock (monitor)
            {
                for (; n <= max; n++)
                {
                    Console.WriteLine("妈妈:第" + n.ToString() + "块蛋糕做好了");
                    //Pulse方法不用调用是因为另一个线程中用的是Wait(object,int)方法
                    //该方法使被阻止线程进入了同步对象的就绪队列
                    //是否需要脉冲激活是Wait方法一个参数和两个参数的重要区别
                    //Monitor.Pulse(monitor);
                    //调用Wait方法释放对象上的锁并阻止该线程(线程状态为WaitSleepJoin)
                    //该线程进入到同步对象的等待队列,直到其它线程调用Pulse使该线程进入到就绪队列中
                    //线程进入到就绪队列中才有条件争夺同步对象的所有权
                    //如果没有其它线程调用Pulse/PulseAll方法,该线程不可能被执行
                    Monitor.Wait(monitor);
                }
            }
        }

public void Consume()
        {
            lock (monitor)
            {
                while (true)
                {
                    //通知等待队列中的线程锁定对象状态的更改,但不会释放锁
                    //接收到Pulse脉冲后,线程从同步对象的等待队列移动到就绪队列中
                    //注意:最终能获得锁的线程并不一定是得到Pulse脉冲的线程
                    Monitor.Pulse(monitor);
                    //释放对象上的锁并阻止当前线程,直到它重新获取该锁
                    //如果指定的超时间隔已过,则线程进入就绪队列
                    Monitor.Wait(monitor,1000);
                    Console.WriteLine("孩子:开始吃第" + n.ToString() + "块蛋糕");
                }
            }
        }

static void Main(string[] args)
        {
            MonitorSample obj = new MonitorSample();
            Thread tProduce = new Thread(new ThreadStart(obj.Produce));
            Thread tConsume = new Thread(new ThreadStart(obj.Consume));
            //Start threads.
            tProduce.Start();
            tConsume.Start();

Console.ReadLine();
        }
    }

这个例子的目的是要理解Wait和Pulse如何保证线程同步的,同时要注意Wait(obeject)和Wait(object,int)方法的区别,理解它们的区别很关键的一点是要理解同步的对象包含若干引用,其中包括对当前拥有锁的线程的引用、对就绪队列(包含准备获取锁的线程)的引用和对等待队列(包含等待对象状态更改通知的线程)的引用。

posted on 2009-10-30 11:33 NET未来之路 阅读(...) 评论(...) 编辑 收藏

转载于:https://www.cnblogs.com/lonelyxmas/archive/2009/10/30/1592931.html

多线程:C#线程同步lock,Monitor,Mutex,同步事件和等待句柄(上)相关推荐

  1. C#多线程同步事件及等待句柄

    最近捣鼓了一下多线程的同步问题,发现其实C#关于多线程同步事件处理还是很灵活,这里主要写一下,自己测试的一些代码,涉及到了AutoResetEvent 和 ManualResetEvent,当然还有也 ...

  2. C语言线程lock与unlock,谈谈线程同步Lock和unLock

    Lock可以使用Condition进行线程之间的调度,它有更好的灵活性,而且在一个对象里面可以有多个Condition(即对象监视器),则线程可以注册在不同的Condition,从而可以 有选择性的调 ...

  3. C#多线程之线程池篇1

    在C#多线程之线程池篇中,我们将学习多线程访问共享资源的一些通用的技术,我们将学习到以下知识点: 在线程池中调用委托 在线程池中执行异步操作 线程池和并行度 实现取消选项 使用等待句柄和超时 使用计时 ...

  4. C#中使用Monitor类、Lock和Mutex类来同步多线程的执行(转)

    C#中使用Monitor类.Lock和Mutex类来同步多线程的执行 在多线程中,为了使数据保持一致性必须要对数据或是访问数据的函数加锁,在数据库中这是很常见的,但是在程序中由于大部分都是单线程的程序 ...

  5. [转]C#中使用Monitor类、Lock和Mutex类来同步多线程的执行

    C#中使用Monitor类.Lock和Mutex类来同步多线程的执行 在多线程中,为了使数据保持一致性必须要对数据或是访问数据的函数加锁,在数据库中这是很常见的,但是在程序中由于大部分都是单线程的程序 ...

  6. C#使用Monitor类、Lock和Mutex类进行多线程同步

    在多线程中,为了使数据保持一致性必须要对数据或是访问数据的函数加锁,在数据库中这是很常见的,但是在程序中由于大部分都是单线程的程序,所以没有加锁的必要,但是在多线程中,为了保持数据的同步,一定要加锁, ...

  7. 线程同步 – lock和Monitor

    在多线程代码中,多个线程可能会访问一些公共的资源(变量.方法逻辑等等),这些公共资源称为临界区(共享区):临界区的资源是不安全,所以需要通过线程同步对多个访问临界区的线程进行控制. 同样,有些时候我们 ...

  8. 秒杀多线程第七篇 经典线程同步 互斥量Mutex

    阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇一个经典的多线程同步问题> <秒杀多线程第五篇经典线程同步关键段CS> <秒杀多线程第六篇经典线程同步事件Event& ...

  9. Java多线程编程-(5)-使用Lock对象实现同步以及线程间通信

    前几篇: Java多线程编程-(1)-线程安全和锁Synchronized概念 Java多线程编程-(2)-可重入锁以及Synchronized的其他基本特性 Java多线程编程-(3)-线程本地Th ...

最新文章

  1. 剑指offer:面试题32 - III. 从上到下打印二叉树 III
  2. centos yum 重新配置dns
  3. log_miner操作(日志挖掘)
  4. c语言字符串倒置,单词倒置,用C++实现,将一句话里的单词进行倒置的方法详解
  5. Javascript – 正则表达式
  6. MySQL学习笔记三:  1. 多表查询     2. 事务     3. DCL
  7. 机器学习工具在数据中心的应用与发展
  8. [导入]C#中的“装箱”与“拆箱”
  9. ehlib的DBGridEh控件中使用过滤功能的方法
  10. iOS开发之tableHeaderView的那些坑
  11. ReactNative之基本组件
  12. 手游和平精英透视教学
  13. 弹窗插件zDialog使用教程
  14. 计算机win7如何连接wifi网络,细说win7怎么共享wifi
  15. Centos8上安装中文字符集zh_CN.UTF-8
  16. system32下 exe文件作用
  17. 修复升级ndk到17.0.4754217编译so失败问题
  18. AD(altium designer)15原理图与PCB设计教程(六)——印制电路板设计的基础知识
  19. 金戒指用计算机怎么算,大姐拿来一个金戒指,先卖后当有蹊跷,鉴定后发现有问题,假的...
  20. Python 爬取懂车帝详情页“全部车型模块信息”!懂车帝就火起来了吗?

热门文章

  1. TCP四次握手连接释放
  2. javascript学习之数组的使用一 push pop shift unshift 方法
  3. [Python] 将两个列表合并为字典
  4. [Python] random.uniform( ) 函数教程与实例解析
  5. k8s中的endpoint
  6. matlab2c使用c++实现matlab函数系列教程- poly函数
  7. jquery删除替换元素remove、detach、empty、replaceWith、replaceAll
  8. python绘制心形图像
  9. 记2020年秋季学期的微波期末考试
  10. leetcode string