.NET 线程 - 同步对象
我有一个多线程应用程序。
一个线程插入队列,许多线程从该队列读取。为了正确读取,读取器线程会像下面的代码一样锁定队列。
我的问题是:当读取器线程调用以下代码时,插入器线程是否会被阻塞,因为它使用相同的队列?还是继续插入而不中断?
lock ( MsgQueue ) {
if ( MsgQueue.Count == 0 ) {
Monitor.Wait( MsgQueue );
continue;
}
msg = MsgQueue.Dequeue( );
}
I have a multi thread application.
One thread inserts in a queue and many thread reads form this queue. In order to read properly, reader threads lock the queue like the following code.
My question is: Does the inserter thread become blocked when the following code is called by reader threads since it uses the same queue? Or it continues inserting without interruption?
lock ( MsgQueue ) {
if ( MsgQueue.Count == 0 ) {
Monitor.Wait( MsgQueue );
continue;
}
msg = MsgQueue.Dequeue( );
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
另一个线程将被锁 (
MsgQueue
) 阻塞,而该线程位于锁
中,但在Monitor 中时则不然
。等待(释放锁,以便其他线程可以
Pulse
)。这是条件变量模式:在处理共享状态(队列实例)时保持锁定,但在等待条件更改时释放锁定(
Monitor.Wait
)。更新:基于评论:
那么队列对象很可能被破坏。除非您使用的队列类型本质上是线程安全的,否则您必须对所有操作使用相同锁。
更新 #2:如果此队列主要用于将对象从一组(源)线程传输到另一组(工作)线程(其中每组可能只有一个),那么您应该考虑a
ConcurrentQueue
是线程安全的(尽管你将需要类似的东西事件,表示队列中有需要避免的内容工人投票)。The other thread will be blocked by the lock (
MsgQueue
) while this thread is in thelock
but not when in theMonitor.Wait
(which releases the lock so other threads canPulse
).This is the conditional variable pattern: hold the lock while working on the shared state (the queue instance), but release it while waiting for the condition to change (the
Monitor.Wait
).Update: based on comment:
Then the queue object is likely to be corrupted. Unless the queue type you are using is inherently thread-safe you must use the same lock for all operations.
Update #2: If this queue is primarily being used to transfer objects from one set of (source) threads to another set of (worker) threads (where each set might just be one) then you should consider a
ConcurrentQueue
which is thread safe (albeit you will need something like an event to signal there is something on the queue to avoid workers polling).是的,当消费者持有锁时,生产者(或插入者)将被阻塞。请注意,锁是通过调用
Monitor.Wait
释放的,然后在控制流返回到调用者时重新获取。所有这些都假设您的生产者尝试获取相同的锁。附带说明一下,消费者编码的方式效率稍低。因为您有一个 continue 语句,所以我必须假设 while 循环包装了 lock,这可能使您的代码看起来更像以下内容。
可以对此进行重构,以便在
lock
块内重新检查等待条件。这样您就不必释放并重新获取锁来执行检查。同样,因为我看到
continue
语句,所以我假设您知道在Wait
之后必须始终重新检查等待条件。但是,以防万一您不知道此要求,我将在此说明,因为它很重要。如果没有重新检查等待条件并且有 2 个或更多消费者,那么其中一个消费者可能会进入锁并使最后一项出列。即使另一个消费者通过调用
Pulse
或PulseAll
从等待队列移动到就绪队列,这种情况仍然可能发生,但它没有机会重新获取第一个消费者之前的锁。显然,如果没有重新检查,消费者可能会尝试对空队列进行操作。生产端使用Pulse
还是PulseAll
并不重要。仍然存在一个问题,因为Monitor
并未优先考虑Wait
优先于Enter
。更新:
我忘了指出,如果您使用的是.NET 4.0,那么您可以利用BlockingCollection 这是阻塞队列的实现。它对于多个生产者和消费者来说是安全的,并且如果队列为空,它会为您完成所有阻塞。
Yes, the producer (or inserter) will be blocked while the lock is held by the consumer. Note that the lock is released by a call to
Monitor.Wait
and then reacquired when control flow has returned back to the caller. All of this assumes your producer attempts to acquire the same lock.As a side note, the way you have the consumer coded is slightly less efficient than it could be. Because you have a
continue
statement I have to assume that awhile
loop wraps thelock
which probably makes your code look more like the following.This could be refactored so that the wait condition is rechecked inside the
lock
block. This way you do not have to release and reacquire the lock to perform the check.Again, because I see the
continue
statement I am assuming you are aware that the wait condition must always be rechecked after aWait
. But, just in case you are not aware of this requirement I will state it here because it is important.If the wait condition is not rechecked and there is 2 or more consumers then one of them could get inside the lock and dequeue the last item. This could still happen even if the other consumer were moved from the waiting queue to the ready queue via a call to
Pulse
orPulseAll
, but it did not get a chance to reacquire the lock before the first consumer. Obviously, without the recheck a consumer could attempt to operate on an empty queue. It does not matter whetherPulse
orPulseAll
is used on the producing side. There is still a problem because theMonitor
does not give preference to aWait
above anEnter
.Update:
I forgot to point out that if you are using .NET 4.0 then you can take advantage of BlockingCollection which is an implementation of a blocking queue. It is safe for multiple producers and consumers and does all of the blocking for you if the queue is empty.
插入器线程在某些地方被阻塞,是的。
在第 1 行,锁由读取器持有,因此插入器被阻止。
在第 2 行,锁被释放,并且不会重新获取,直到插入器可能在
MsgQueue
上调用Monintor.Pulse
。在第 3 行,锁仍然被持有(从第 1 行开始),之后由于退出
lock
范围而再次释放。The inserter thread is being blocked at points, yes.
At line 1 the lock is held by the reader, so the inserter is blocked.
At line 2 the lock is released, and not reacquired until the inserter presumably calls
Monintor.Pulse
onMsgQueue
.At line 3 the lock is still being held (from line 1), and afterwards it is released again due to exiting the
lock
scope.如果插入器线程调用lock(MsgQueue),那么显然只要其中一个读者锁定了队列,它就会阻塞
If the inserter thread calls
lock ( MsgQueue )
then obviously it will block whenever one of the readers has locked the queue不,我认为您的问题是关于锁(MsgQueue)的含义,并且这个比喻可能有点误导。锁定对象不会以任何方式更改该对象的状态,也不会阻止其他线程,除非这些线程也在同一对象上使用
lock
。这就是为什么您经常看到这种(更好的)模式:
锁中使用的引用仅充当“门票”。
No. I think your questuon is about the meaning of
lock ( MsgQueue )
and the metaphor can be a bit misleading. Locking on an object does not change the state of that object in any way, nor does it block other threads, unless those threads uselock
on the same object too.That's why you often see this (better) pattern:
The reference used in the lock only serves as a 'ticket'.