多线程中的lock,Monitor.Wait和Monitor.Pulse

gomoku 2011-12-09 06:18:07
加精
有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编译展开的一点有趣变化。

朋友们的原贴可以在随后的注释中找到链接。
...全文
11338 125 打赏 收藏 转发到动态 举报
写回复
用AI写文章
125 条回复
切换为时间正序
请发表友善的回复…
发表回复
pushouli 2014-08-17
  • 打赏
  • 举报
回复
在第3个时间点时,为什么B和C直接进入了就绪队列,而不是等待队列中?
thinkpi 2013-05-16
  • 打赏
  • 举报
回复
霸气的讲解!!!
youhao1999 2011-12-31
  • 打赏
  • 举报
回复
谢谢分享[Quote=引用楼主 gomoku 的回复:]
有CSDN的朋友问一个问题,“Lock关键字不是有获取锁、释放锁的功能吗?...为什么还需要执行Pulse?”
也有朋友有些疑点,“用lock就不要用monitor了”,“Monitor.Wait完全没必要”,“为什么Pulse和Wait方法必须从同步的代码块内调用?”

这些疑问很自然。在大部分情况下,lock确实能基本达到我们要求资源同步的目的,加上配合其他同步工具,比如事件(AutoR……
[/Quote]
tqxiaofang 2011-12-29
  • 打赏
  • 举报
回复
怎麼得到論壇的積分啊、
wy811007 2011-12-24
  • 打赏
  • 举报
回复
有木有 结合 winform界面程序 的例子啊 多线程 感觉很难哦 不明白
Lvyonglvyong 2011-12-19
  • 打赏
  • 举报
回复
TransactionScope应该可以实现
Icedmilk 2011-12-13
  • 打赏
  • 举报
回复
TransactionScope是处理数据库事务的吧

另外,楼上各位好像把原子操作的定义弄混了

其实那个函数ReliableEnter
完全可以先设置flag再加锁啊

[Quote=引用 89 楼 youbl 的回复:]
你说的让开发者去防范,但是人去防范,总有意外,所以如果能程序实现原子操作最好
另外Abort的情况也是很常见的,比如购票进行中,忽然不想买了,便取消,或强制关闭了程序等

至于原子操作,TransactionScope应该可以实现吧?没用过这个类,一直都是用DbTransaction去实现sql的原子操作
另外,把2行代码放在类似lock的代码里,应该也可以实现原子吧?



引用……
[/Quote]
游北亮 2011-12-13
  • 打赏
  • 举报
回复
你说的让开发者去防范,但是人去防范,总有意外,所以如果能程序实现原子操作最好
另外Abort的情况也是很常见的,比如购票进行中,忽然不想买了,便取消,或强制关闭了程序等

至于原子操作,TransactionScope应该可以实现吧?没用过这个类,一直都是用DbTransaction去实现sql的原子操作
另外,把2行代码放在类似lock的代码里,应该也可以实现原子吧?


[Quote=引用 85 楼 lizhibin11 的回复:]
Icedmilk你说得对,我回复完后觉得回复有误,只是随后想编程时不可能发生主动调用Abort的情况(这本身意味着可能会出意外,应该采用专门的退出线程信号量或者条件判断),而且即使一定要强行这样去做,finally也可以让开发者尽量采取措施去防范这种意外。
Monitor.Enter(object obj, ref bool lockTaken)这个应该是来自Windows改进后的带返回值的Tr……
[/Quote]
lizhibin11 2011-12-13
  • 打赏
  • 举报
回复
很感谢楼主的文章,我以前没有注意过Monitor.Wait这个方法,看了楼主的文章后,受益匪浅,也很高兴有更轻量级的方法某种程度代替WaitHandle,只是随后确认时引发了一系列问题,让我了解了很多东西,看到这样的文章让人兴奋,谢谢楼主。
Icedmilk 2011-12-13
  • 打赏
  • 举报
回复
与其纠结在这,还不如先去把MSDN上关于Monitor这个类的文档先阅读一下。
下面是MSDN的原文:
It is important to note the distinction between use of Monitor and WaitHandle objects. Monitor objects are purely managed, fully portable, and might be more efficient in terms of operating-system resource requirements. WaitHandle objects represent operating-system waitable objects, are useful for synchronizing between managed and unmanaged code, and expose some advanced operating-system features like the ability to wait on many objects at once.


[Quote=引用 44 楼 lizhibin11 的回复:]
经测试,AutoResetEvent的效率和Monitor.Wait几乎一样,有心人可以测试对比一下。
Monitor.Enter等待时间如果很长,也是会进入内核态的,它对应的是win32的CRITICAL_SECTION结构。但是Monitor.Wait对应哪个win32 api我没找到,或者它是.net新创了一个以前win32所没有的东西,我怀疑Monitor.Wait也会进入内核态,在这点……
[/Quote]
gomoku 2011-12-13
  • 打赏
  • 举报
回复
打算下午结贴。给分的时候永远不会公平,请大家海涵。

并多谢大家的支持和鼓励。
特别谢谢阿非帮忙修改和整理回贴。
非常谢谢lizhibin11提出了思辨和挑战的问题。

如果我重新写该文,应该不会再有Monitor和WaitHandle的效率比较:
一、既没有参考引用,也没有测试结果。
二、两个东西有不同的用途,扯在一起比较不公平。

之所以写MyManualEvent的例子,是想加深印象,又不想套用微软的"生产者-消费者"范例。
它不太能体现Monitor的特色,也可能重写。
lizhibin11 2011-12-13
  • 打赏
  • 举报
回复
Icedmilk你说得对,我回复完后觉得回复有误,只是随后想编程时不可能发生主动调用Abort的情况(这本身意味着可能会出意外,应该采用专门的退出线程信号量或者条件判断),而且即使一定要强行这样去做,finally也可以让开发者尽量采取措施去防范这种意外。
Monitor.Enter(object obj, ref bool lockTaken)这个应该是来自Windows改进后的带返回值的TryEnterCriticalSection,对CRITICAL_SECTION结构的某个字段的修改可以是原子操作,但同时赋值给lockTaken我认为不可能是原子操作(你能举出同时修改两个变量的原子操作吗?)。如果不调用Abort,也不会发生这种人为的“意外”?
Icedmilk 2011-12-13
  • 打赏
  • 举报
回复
你先把别人的问题看明白了再回复行不?
人家问的是加锁操作和设置那个bool的flag能不能确保是原子操作
如果不是原子操作,可能加了锁之后,还没来的急设置flag为true就会被abort跳到final里,
if (lockTaken) Monitor.Exit(lockObj);
就不会释放锁

[Quote=引用 72 楼 lizhibin11 的回复:]
引用 70 楼 youbl 的回复:

用Reflecor看了下4.0里的Monitor.Enter(object obj, ref bool lockTaken)方法
最后是调用:
[MethodImpl(MethodImplOptions.InternalCall), SecurityCritical]
private static extern void ReliableEnter(object obj, ref bool……

finally块会在调用Abort后线程中止前执行(.net 1.0和1.1例外),看50楼代码。
[/Quote]
ylywyn136 2011-12-13
  • 打赏
  • 举报
回复
恩,Monitor是相当于windows里的 关键段+条件变量. <<windows高级编程>>里有提到
wangqifei8231873 2011-12-13
  • 打赏
  • 举报
回复
我觉得再一次执行Pulse能更好地用于控制。
lizhibin11 2011-12-13
  • 打赏
  • 举报
回复
回复youbl,Abort意味着我告诉操作系统可以在过程的任一一点上按它的意愿中止,我接受这个不可控的结果,我不在乎意外与错误。所以多线程编程的开发者几乎不会以这种方式中止线程。
原子操作是指,一个过程绝对不会被中断,在完成这个过程期间不会插入其他操作。关键代码段只有在进入和离开的两个节点上,在未进入内核态的情况下,会采用原子操作。
threenewbee 2011-12-12
  • 打赏
  • 举报
回复
不好意思,我说一句废话,阿非删除不用通知我了。

我就想说,这个贴是昨天吃早餐的时候方兴和我提到的。所以我一定要瞻仰下。
lizhibin11 2011-12-12
  • 打赏
  • 举报
回复
这两篇文章我都看了,我的疑问就是Monitor.Wait性能是否比AutoResetEvent高,因为在相对WaitHanle缺失很多功能以及编程的方便性的情况下,如果性能并没有提高,是得不偿失的。
您提供的第二篇文章我觉得效率都不是很高,后面采用CRITICAL_SECTION的方式也是使用了内核对象来辅助。即使用C#来实现我觉得也有更好的方式,可以先采用自旋锁或者Interlocked.CompareExchange实现自旋一定周期后,转入等待内核对象的信号。这样效率是否高于直接使用内核对象也不好说,比如自旋一定周期后,没有获得锁,开始缓慢的进入内核态的过程,而在这个过程之初,其实锁已经被另一个线程释放了,还不如不自旋直接进入内核态。
即使是在Vista下,条件变量在某些情况中也只是“可能更高效”。总之前天测试的时候,长等待也用过,极短的等待也用过,性能几乎一样。没有得到透彻的解释。
gomoku 2011-12-12
  • 打赏
  • 举报
回复
[Quote=引用 69 楼 lizhibin11 的回复:]
Windows Vista新增了条件变量,...
[/Quote]

.Net中的Monitor实现更加接近wiki中Monitor的概念(参考1),比如对Conditional Variable的支持。
而当时用Win32来实现则相对麻烦(参考2)。

1. Wiki Monitor(英文版比中文版好):
http://en.wikipedia.org/wiki/Monitor_(synchronization)

2. Strategies for Implementing POSIX Condition Variables on Win32
http://www.cs.wustl.edu/~schmidt/win32-cv-1.html
阿非 2011-12-12
  • 打赏
  • 举报
回复
技术分享是希望能够帮助更多需要帮助的人,如果大家有与本帖内容相关的疑问可以提出来,LZ有时间肯定会做出解答的,大家也可以一起集思广益。如果有人能分享心得体会那更是求之不得。
所以还请大家不要无意义回复,试想你对此类问题有疑问,却翻了好几页没找到关键的部分,满篇的灌水什么心情,如果你觉得本文对你帮助很大,想查阅又怕记忆繁琐可以点击标题右侧的收藏。
本回复之前的无意义回复,都会通知回复人之后删除,由于精力有限本回复之后的无意义回复删除恕不另行通知。
加载更多回复(15)
1.几种同步方法的区别 lockMonitor是.NET用一个特殊结构实现的,Monitor对象是完全托管的、完全可移植的,并且在操作系统资源要求方 面可能更为有效,同步速度较快,但不能跨进程同步。lockMonitor.Enter和Monitor.Exit方法的封装),主要作用是锁定临界区,使临 界区代码只能被获得锁的线程执行。Monitor.WaitMonitor.Pulse用于线程同步,类似信号操作,个人感觉使用比较复杂,容易造成死 锁。 互斥体Mutex和事件对象EventWaitHandler属于内核对象,利用内核对象进行线程同步,线程必须要在用户模式和内核模 式间切换,所以一般效率很低,但利用互斥对象和事件对象这样的内核对象,可以在多个进程的各个线程间进行同步。 互斥体Mutex类似于一个接力棒,拿到接力棒的线程才可以开始跑,当然接力棒一次只属于一个线程(Thread Affinity),如果这个线程不释放接力棒(Mutex.ReleaseMutex),那么没办法,其他所有需要接力棒运行的线程都知道能等着看热 闹。 EventWaitHandle 类允许线程通过发信号互相通信。 通常,一个或多个线程在 EventWaitHandle 上阻止,直到一个未阻止的线程调用 Set 方法,以释放一个或多个被阻止的线程。 2.什么时候需要锁定 首先要理解锁定是解决竞争条件的,也就是多个线程同时访问某个资源,造成意想不到的结果。比如,最简单的情况是,一个计数器,两个线程 同时加一,后果就是损失了一个计数,但相当频繁的锁定又可能带来性能上的消耗,还有最可怕的情况死锁。那么什么情况下我们需要使用锁,什么情况下不需要 呢? 1)只有共享资源才需要锁定 只有可以被多线程访问的共享资源才需要考虑锁定,比如静态变量,再比如某些缓存的值,而属于线程内部的变量不需要锁定。 2)多使用lock,少用Mutex 如果你一定要使用锁定,请尽量不要使用内核模块的锁定机制,比如.NET的Mutex,Semaphore,AutoResetEvent和 ManuResetEvent,使用这样的机制涉及到了系统在用户模式和内核模式间的切换,性能差很多,但是他们的优点是可以跨进程同步线程,所以应该清 楚的了解到他们的不同和适用范围。 3)了解你的程序是怎么运行的 实际上在web开发大多数逻辑都是在单个线程展开的,一个请求都会在一个单独的线程处理,其的大部分变量都是属于这个线程的,根本没有必要考虑锁 定,当然对于ASP.NET的Application对象的数据,我们就要考虑加锁了。 4)把锁定交给数据库 数 据库除了存储数据之外,还有一个重要的用途就是同步,数据库本身用了一套复杂的机制来保证数据的可靠和一致性,这就为我们节省了很多的精力。保证了数据源 头上的同步,我们多数的精力就可以集在缓存等其他一些资源的同步访问上了。通常,只有涉及到多个线程修改数据库同一条记录时,我们才考虑加锁。 5)业务逻辑对事务和线程安全的要求 这 条是最根本的东西,开发完全线程安全的程序是件很费时费力的事情,在电子商务等涉及金融系统的案例,许多逻辑都必须严格的线程安全,所以我们不得不牺牲 一些性能,和很多的开发时间来做这方面的工作。而一般的应用,许多情况下虽然程序有竞争的危险,我们还是可以不使用锁定,比如有的时候计数器少一多一, 对结果无伤大雅的情况下,我们就可以不用去管它。 3.InterLocked类 Interlocked 类提供了同步对多个线程共享的变量的访问的方法。如果该变量位于共享内存,则不同进程的线程就可以使用该机制。互锁操作是原子的,即整个操作是不能由相 同变量上的另一个互锁操作所断的单元。这在抢先多线程操作系统是很重要的,在这样的操作系统,线程可以在从某个内存地址加载值之后但是在有机会更改 和存储该值之前被挂起。

62,046

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术交流专区
javascript云原生 企业社区
社区管理员
  • ASP.NET
  • .Net开发者社区
  • R小R
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

.NET 社区是一个围绕开源 .NET 的开放、热情、创新、包容的技术社区。社区致力于为广大 .NET 爱好者提供一个良好的知识共享、协同互助的 .NET 技术交流环境。我们尊重不同意见,支持健康理性的辩论和互动,反对歧视和攻击。

希望和大家一起共同营造一个活跃、友好的社区氛围。

试试用AI创作助手写篇文章吧