Monitor.Wait/Pulse 的这种使用是否存在竞争条件?
我有一个简单的生产者/消费者场景,其中只生产/消费一个项目。此外,生产者在继续之前会等待工作线程完成。我意识到这消除了多线程的全部意义,但请假设它确实需要这样(:
这段代码无法编译,但我希望你明白:
// m_data is initially null
// This could be called by any number of producer threads simultaneously
void SetData(object foo)
{
lock(x) // Line A
{
assert(m_data == null);
m_data = foo;
Monitor.Pulse(x) // Line B
while(m_data != null)
Monitor.Wait(x) // Line C
}
}
// This is only ever called by a single worker thread
void UseData()
{
lock(x) // Line D
{
while(m_data == null)
Monitor.Wait(x) // Line E
// here, do something with m_data
m_data = null;
Monitor.Pulse(x) // Line F
}
}
这是我不确定的情况:
假设许多线程使用不同的输入调用 SetData()。 只有其中一人会进入锁内,其余人将被封锁在A线上。 假设进入锁的线程设置了 m_data 并进入 C 行。
问题:C 行上的 Wait() 能否允许 A 行上的另一个线程获取锁并覆盖 m_data 在工作线程到达之前?
假设这种情况没有发生,并且工作线程处理原始的 m_data,并最终到达 F 行,那么当 Pulse() 关闭时会发生什么?
是否只有在C行等待的线程才能获得锁?或者它会与 A 线上等待的所有其他线程竞争吗?
本质上,我想知道 Pulse()/Wait() 是否在“幕后”专门相互通信,或者它们是否与 lock() 处于同一级别。
这些问题的解决方案(如果存在的话)当然是显而易见的 - 只需用另一个锁包围 SetData() - 例如,lock(y)。 我只是好奇这是否是一个问题。
I have a simple producer/consumer scenario, where there is only ever a single item being produced/consumed. Also, the producer waits for the worker thread to finish before continuing. I realize that kind of obviates the whole point of multithreading, but please just assume it really needs to be this way (:
This code doesn't compile, but I hope you get the idea:
// m_data is initially null
// This could be called by any number of producer threads simultaneously
void SetData(object foo)
{
lock(x) // Line A
{
assert(m_data == null);
m_data = foo;
Monitor.Pulse(x) // Line B
while(m_data != null)
Monitor.Wait(x) // Line C
}
}
// This is only ever called by a single worker thread
void UseData()
{
lock(x) // Line D
{
while(m_data == null)
Monitor.Wait(x) // Line E
// here, do something with m_data
m_data = null;
Monitor.Pulse(x) // Line F
}
}
Here is the situation that I am not sure about:
Suppose many threads call SetData() with different inputs.
Only one of them will get inside the lock, and the rest will be blocked on Line A.
Suppose the one that got inside the lock sets m_data and makes its way to Line C.
Question: Could the Wait() on Line C allow another thread at Line A to obtain the lock and overwrite m_data before the worker thread even gets to it?
Supposing that doesn't happen, and the worker thread processes the original m_data, and eventually makes its way to Line F, what happens when that Pulse() goes off?
Will only the thread waiting on Line C be able to get the lock? Or will it be competing with all the other threads waiting on Line A as well?
Essentially, I want to know if Pulse()/Wait() communicate with each other specially "under the hood" or if they are on the same level with lock().
The solution to these problems, if they exist, is obvious of course - just surround SetData() with another lock - say, lock(y).
I'm just curious if it's even an issue to begin with.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
无法保证消费者将在另一个生产者之前进入等待或就绪队列。
C# 和 Java 风格监视器在 Wikipedia 的“隐式条件监视器”下进行了描述.
很好地概述了
Monitor
中发生的情况(取自 this 优秀网站):假设
SetData()
由两个生产者线程 P1 和 P1 调用。 P2。消费者线程 C1 也启动。
P1、P2 和 C1 均进入就绪队列。
P1 首先获取锁。
等待队列为空,
line B
上的Pulse()
无效。P1 在
C 线
等待,因此被放入等待队列。就绪队列中的下一个线程获取锁。
它同样可以是 P2 或 C1 - 在第一种情况下,断言失败。
你有一个竞争条件。
它将把一个服务员从等待队列移动到就绪队列。
锁由发出
Pulse()
的线程持有。在脉冲线程释放锁后,被通知的线程将有机会获取锁(就绪队列中可能已经有其他线程)。
来自 MSDN , Monitor.Pulse():
“当前拥有指定对象上的锁的线程调用此方法来向队列中的下一个线程发出信号以获取锁。收到脉冲后,等待线程将移至就绪队列。当调用 Pulse 的线程释放锁,就绪队列中的下一个线程(不一定是被脉冲的线程)获取锁。”
就绪队列中的所有线程都会“竞争”下一个锁。
他们是直接到达那里还是通过
Pulse()
从等待队列到达那里并不重要。“队列”可以通过其他方式实现。 (不是队列数据结构本身)。
这样,
Monitor
实现可能无法保证公平性 - 但可能具有更高的整体吞吐量/性能。There is no guarantee that the consumer will be enqueued on the waiting or ready queue before another producer.
C# and Java style monitors are described on Wikipedia, under "Implicit condition monitors".
A nice overview of what happens in
Monitor
(taken from this excellent site):Suppose that
SetData()
is called by two producer threads, P1 & P2.A consumer thread, C1 is started as well.
P1, P2 and C1 all enter the ready queue.
P1 acquires the lock first.
The waiting queue is empty,
Pulse()
online B
has no effect.P1 waits on
line C
, so it is placed on the waiting queue.The next thread in the ready queue acquires the lock.
It can equally be P2 or C1 - in the first case, the assertion fails.
You have a race condition.
It will move a waiter from the waiting queue to the ready queue.
The lock is held by the thread that issues the
Pulse()
.The notified thread will get a chance to acquire the lock after the pulsing thread releases the lock (there can already be others in the ready queue).
From MSDN, Monitor.Pulse():
"The thread that currently owns the lock on the specified object invokes this method to signal the next thread in line for the lock. Upon receiving the pulse, the waiting thread is moved to the ready queue. When the thread that invoked Pulse releases the lock, the next thread in the ready queue (which is not necessarily the thread that was pulsed) acquires the lock."
All threads that are on the ready queue "compete" for having the lock next.
It does not matter if they got there straight or from the waiting queue by means of
Pulse()
.The "queues" might be implemented by other means. (Not the queue data structure as such).
This way a
Monitor
implementation may not guarantee fairness - but may have higher overall throughput/performance.