【问题标题】:AutoResetEvent, ManualResetEvent vs MonitorAutoResetEvent、ManualResetEvent 与监视器
【发布时间】:2010-12-15 14:08:17
【问题描述】:

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

从性能的角度来看,有没有哪一个比其他的性能更好?

我问这个是因为我已经编码了一段时间,但对这个主题没有适当的了解。

【问题讨论】:

  • 只需编写一个测试应用程序,使其性能倍增。
  • 一个监视器(和等效的 C# lock 语句)维护一个内部队列,保证线程按照它们被阻塞的顺序被释放。自动/手动重置事件要简单得多,并且没有这种开销。

标签: .net concurrency synchronization .net-3.5


【解决方案1】:

WaitHandles 看起来与 Wait/Pulse Constructs 非常相似,但在细节上有所不同:WaitHandles Set 方法,即使没有线程在等待,也会设置 Signal。这意味着如果您在一个线程中调用 Set,然后在同一等待句柄上的另一个线程中调用 WaitOne,则第二个线程将继续。 Wait 和 Pulse 不同,Pulse 仅向已经在等待队列中的线程发出信号。这意味着如果您在一个线程中调用 Pulse,然后在同一对象上的另一个线程中调用 Wait,则第二个线程将永远等待(死锁)。如果使用 Wait 和 Pulse,您必须非常小心,只有在您知道自己在做什么时才使用它,否则您可能会很幸运...

要使用 Monitor、weather AutoReset 或 ManualReset 自己创建 WaitHandle 的行为,您需要做的不仅仅是简单的 Wait/Pulse 构造。 只需使用完成工作所需的工具

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

【讨论】:

  • 我不同意您对使用 Wait/Pulse 的担忧。 It's easy to use them correctly 不会冒死锁的风险。 Wait/Pulse 是线程间信号传递的核心构建块,它们的性能可以优于 *ResetEvent,因为正如 Gonzalo 正确指出的那样,后者可用于进程间信号传递,从而产生开销。
  • @Eugene:Wait/Pulse的错误使用很容易导致死锁。即使是 Monitor.Pulse 方法的 MSDN example 也有竞争条件。如果他们示例中的第二个线程设法在第一个线程之前启动,它将死锁。
  • 只是想补充一点,如果你想进入 .NET 线程,EugeneBeresovksy 在他的第一条评论中提供的链接是最好的起点之一。
【解决方案2】:

如果可以,请使用 Monitor。它类似于 CRITICAL_SECTION。 AutoResetEvent/ManualResetEvent 的开销可能会稍多一些,因为它们可以由不同的进程共享,而 Monitor 属于单个进程。

【讨论】:

  • 进程之间不共享自动重置事件(互斥体是)。但它们是原生 Windows 对象,如果使用太多可能会遇到麻烦。
  • 来自msdn.microsoft.com/en-us/library/ms682400(VS.85).aspx 看起来 AutoReset 也可以共享。至少没有什么可以阻止您创建带有名称的 Auto 事件,但 AutoResetEvent 和 ManualResetEvent 不允许您传递“名称”。
  • 只需使用基类 EventWaitHandle。 AutoResetEvent 和 ManualResetEvent 只是构造函数包装器,它们唯一要做的就是使用 EventResetMode.AutoReset 或 EventResetMode.ManualReset 从 EventWaitHandle 调用构造函数...
【解决方案3】:

如果您遵循一些简单的规则,请等待和脉搏:

  1. 如果您的对象不需要锁定并且您可以提前告知它已准备好,您可能不会费心去锁定;但是,如果看起来您必须等待,则必须获取锁,然后确保在实际执行之前仍然确实必须等待;在大多数情况下,您还应该假设无论条件是否准备好,您都可能被随机唤醒,因此您应该在每次醒来时重新检查条件,并在需要时重新等待。
  2. 必须在 条件设置后给出脉冲,以允许等待代码继续。
  3. 如果您有类似“退出”标志的东西,那么让段获取锁的代码在释放锁后测试退出标志可能会有所帮助,如果设置了该标志,重新获取锁并发送脉冲。然后,您可以让退出例程设置标志,尝试以零超时获取锁,并仅在可以获取锁时发送脉冲。如果采集失败,您可以放心,其他一些代码将发送必要的脉冲。这将避免“退出”例程卡在锁上的任何可能性。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-04
    • 1970-01-01
    相关资源
    最近更新 更多