AutoResetEvent、ManualResetEvent和Monitor的区别

19

假设我需要在.Net 3.5 SP1中编排同步算法,并且在标题中列出的任何同步原语都非常适合此任务。

从性能角度来看,这些同步原语中有哪个更加高效?

我问这个问题是因为我已经编码了一段时间,但对这个主题没有正确的知识。


注: - ".Net"不翻译; - "synchronization algorithm"可视具体情境酌情翻译; - "synchronization primitives"指同步原语或同步基元,可统一翻译。

只需编写一个测试应用程序来计时它们的性能。 - i_am_jorf
1
一个监视器(以及等效的C#锁语句)维护一个内部队列,保证线程以阻塞的相同顺序被释放。自动/手动重置事件要简单得多,没有这种开销。 - Steve Guidi
3个回答

18

WaitHandles看起来与Wait/Pulse构造非常相似,但区别在于细节:WaitHandles的Set方法会在没有线程等待时设置信号。这意味着如果您在一个线程中调用Set,然后在同一waithandle上的另一个线程中调用WaitOne,第二个线程将继续执行。而Wait和Pulse是不同的,Pulse只会向已经在等待队列中的线程发送信号。这意味着如果您在一个线程中调用了Pulse,然后在同一对象的另一个线程中调用Wait,第二个线程将永远等待(死锁)。使用Wait和Pulse需要非常小心,只有当您知道自己在做什么时才使用,否则可能只是走运...

要使用Monitor创建类似WaitHandle的行为,无论是AutoReset还是ManualReset,都要比简单的Wait/Pulse构造复杂得多。只需使用您需要的工具完成工作即可

如果无法使用简单的锁定或原子操作来同步线程,请考虑使用WaitHandles。如果无法使用WaitHandles同步线程,请考虑使用Wait和Pulse。


4
我不认为使用Wait/Pulse会存在风险,可以轻松正确地使用它们而不会导致死锁。Wait/Pulse是线程间信号传递的核心构建块,其性能可能比*ResetEvent更好,因为后者可用于进程间信号传递,从而产生开销(正如Gonzalo所指出的)。详细信息请参见此链接:http://www.albahari.com/threading/part4.aspx#_Signaling_with_Wait_and_Pulse - Evgeniy Berezovsky
2
@Eugene:不正确使用Wait/Pulse很容易导致死锁。即使是Monitor.Pulse方法的MSDN示例也存在竞争条件。如果在他们的示例中第二个线程在第一个线程之前启动,它将会死锁。 - vgru
1
只是想补充一下,EugeneBeresovksy在他的第一条评论中提供的链接是最好的起点之一,如果你想进入.NET线程。 - haze4real

5
如果可以的话,使用Monitor。它类似于CRITICAL_SECTION。AutoResetEvent/ManualResetEvent可能会有稍微更多的开销,因为这些可以被不同进程共享,而Monitor属于单个进程。

7
自动重置事件在进程之间不共享(互斥量可以),但它们是本机 Windows 对象,如果使用过多可能会遇到问题。 - Yann Schwartz
2
从http://msdn.microsoft.com/en-us/library/ms682400(VS.85).aspx看起来,AutoReset也可以共享。至少没有什么阻止你使用名称创建一个自动事件,但是AutoResetEvent和ManualResetEvent不允许你传递“名称”。 - Gonzalo
3
只需使用基类EventWaitHandle。 AutoResetEvent和ManualResetEvent只是构造函数包装器,它们唯一的作用就是使用EventResetMode.AutoReset或EventResetMode.ManualReset参数调用EventWaitHandle的构造函数。请注意,这些操作不会改变原有意义。 - haze4real

2

如果您遵循一些简单的规则,等待和脉冲是没有问题的:

  1. 如果您的对象不需要锁定并且您可以提前判断它是否准备好,则无需锁定;但是,如果看起来您必须等待,则必须获取锁,并确保您仍然真正需要等待才能实际执行;在大多数情况下,您还应该假定您可能随机被唤醒,无论条件是否准备好,因此每次唤醒时都应重新检查条件并根据需要重新等待。
  2. 条件设置好后,必须给出脉冲以允许等待代码继续。
  3. 如果您有类似于“退出”标志的东西,则在释放锁之后,每个获取锁的代码片段都测试一次退出标志是否已设置;如果已设置,则重新获取锁并发送一个脉冲。然后,您可以编写一个退出程序来设置该标志,并尝试使用零超时获取锁,仅当成功获取锁时才发送脉冲。如果获取失败,则可以确保其他某个代码片段将发送所需的脉冲。这将避免“退出”例程在锁定时被卡住的任何可能性。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接