为啥 Monitor.Wait() 和 Monitor.Pulse() 需要锁?

Posted

技术标签:

【中文标题】为啥 Monitor.Wait() 和 Monitor.Pulse() 需要锁?【英文标题】:Why do Monitor.Wait() and Monitor.Pulse() require a lock?为什么 Monitor.Wait() 和 Monitor.Pulse() 需要锁? 【发布时间】:2021-12-23 13:24:13 【问题描述】:

要调用Monitor.Wait(obj)Monitor.Pulse(obj),首先必须通过lock(obj)Monitor.Enter(obj) 进入监视器。为什么 API 需要这个?

Thread synchronization: Wait and Pulse demystified 暗示了一些关于条件变量等的东西,但我不明白为什么我希望始终使用它。

我的意思是,如果只是实现依赖于获取的锁来完成WaitPulse 的工作,为什么不只是在内部实现它作为它们的一部分呢?为什么不将WaitPulseMonitor 中的其他行为完全解耦,让开发人员随心所欲地使用这些功能?这对开发人员来说似乎是一个毫无意义的负担,但有充分的理由让它成为这样吗?

更具体地说,Monitor.Wait(obj) throws SynchronizationLockException 如果“Wait 不是从同步代码块中调用”。 Monitor.Pulse(obj) throws SynchronizationLockException 如果“调用线程不拥有指定对象的锁”。

【问题讨论】:

您是在问“为什么lock 是使用Monitor 类实现的,而不是相反”或“为什么要使用Wait/Pulse 释放锁需要获取先用lockEnter锁定?”或者你问更多关于为什么像***.com/questions/1559293/…这样的存在? 抱歉,我已经编辑了原始问题以使其更清晰。 不,您的编辑并没有使问题更清楚-您的意思已经很清楚了(例如,每个人都可以查看文档以查看使用模式),但仍不清楚您为什么这么认为对于“作为单个操作获取和释放锁”将具有一定的价值。如果你能解释一下希望Wait 做什么,那就太好了,这样人们就可以尝试澄清这一点。 (旁注你显然知道"EDIT:" should not be added 要发布 - 除非你有一些特别的声明,否则避免这样做) Related @Jake1234 有点不清楚。随意编辑整个内容,或者关闭它并写一个新问题 【参考方案1】:

不持有锁,你肯定会有竞争条件。假设线程 A 将调用 Wait,而线程 B 将调用 Pulse。在线程 B 发送脉冲之前,线程 A已经在等待,这一点很重要。如果线程 B 在线程 A 开始等待之前 发送脉冲,那么脉冲什么也不做,线程 A 将无限期地等待。我们如何知道线程 A 正在等待?好吧,它可以在它开始等待之前发送一条消息(例如,设置一个标志或将一个项目放入队列中),但是如果发送消息然后另一个线程在它开始等待之前被调度呢?仅仅因为它已经发送了它打算开始等待的消息,并不意味着它实际上已经开始等待了!知道线程 A 确实进入等待的唯一方法是让 A 在发送消息时持有锁,在它开始等待时原子地释放锁,让 B 获取锁,然后观察 A 的消息。

现在,好吧,也许你可以设计一种情况,B 确定 A 已经开始等待,释放锁,做一些其他事情,然后想在释放锁后发送一个脉冲来唤醒 A,但是你会当您想再次使用监视器时遇到类似的问题,或者如果您想有更多的线程等待或脉冲。或者也许你说你不在乎错过脉冲,你只会继续发送更多......但随后你就会转向投票并浪费PulseWait的好处。

简而言之,您需要在等待之前持有锁,因为这是可靠地传达有实体在等待脉冲的唯一方法。您需要在发出脉冲之前保持锁定,因为这是可靠地发现有实体准备好接收脉冲的唯一方法。如果没有这些行为,Monitor 将不会成为在其之上构建更高级同步机制的有用原语。

【讨论】:

【参考方案2】:

因为 monitor.wait 的目的是释放对象上的锁。这些是静态方法,因此除了通过参数外,没有状态指示应该释放哪个对象,因此必须传入它

另外,这里的目的是允许锁用于某种目的,然后在 Monitor.Wait 中释放。将其锁定然后由监视器解锁不会有任何效果

【讨论】:

抱歉给您带来了困惑,这不是我要问的。

以上是关于为啥 Monitor.Wait() 和 Monitor.Pulse() 需要锁?的主要内容,如果未能解决你的问题,请参考以下文章

C#简单理解 Monitor.Wait 与 Monitor.Pulse

Monitor.Wait,条件变量

Monitor.Wait 是不是确保重新读取字段?

如何通知解锁线程(Monitor.Wait(),PulseAll()模拟)

不了解 Monitor.Pulse() 的必要性

lock与monitor的区别