.NET 线程 - 同步对象

发布于 2024-11-15 19:33:19 字数 309 浏览 2 评论 0原文

我有一个多线程应用程序。

一个线程插入队列,许多线程从该队列读取。为了正确读取,读取器线程会像下面的代码一样锁定队列。

我的问题是:当读取器线程调用以下代码时,插入器线程是否会被阻塞,因为它使用相同的队列?还是继续插入而不中断?

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

一花一树开 2024-11-22 19:33:19

另一个线程将被锁 (MsgQueue) 阻塞,而该线程位于 中,但在 Monitor 中时则不然。等待(释放锁,以便其他线程可以Pulse)。

这是条件变量模式:在处理共享状态(队列实例)时保持锁定,但在等待条件更改时释放锁定(Monitor.Wait)。

更新:基于评论:

不,它只是简单地插入。插入器没有锁

那么队列对象很可能被破坏。除非您使用的队列类型本质上是线程安全的,否则您必须对所有操作使用相同锁。

更新 #2:如果此队列主要用于将对象从一组(源)线程传输到另一组(工作)线程(其中每组可能只有一个),那么您应该考虑a ConcurrentQueue 是线程安全的(尽管你将需要类似的东西事件,表示队列中有需要避免的内容工人投票)。

The other thread will be blocked by the lock (MsgQueue) while this thread is in the lock but not when in the Monitor.Wait (which releases the lock so other threads can Pulse).

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:

No it inserts simply. There is no lock for inserter

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).

如此安好 2024-11-22 19:33:19

是的,当消费者持有锁时,生产者(或插入者)将被阻塞。请注意,锁是通过调用 Monitor.Wait 释放的,然后在控制流返回到调用者时重新获取。所有这些都假设您的生产者尝试获取相同的锁。

附带说明一下,消费者编码的方式效率稍低。因为您有一个 continue 语句,所以我必须假设 while 循环包装了 lock,这可能使您的代码看起来更像以下内容。

object msg = null;
while (msg == null)
{
  lock (MsgQueue) 
  {
    if (MsgQueue.Count == 0) 
    {
      Monitor.Wait(MsgQueue);
      continue;
    }
    msg = MsgQueue.Dequeue();
  }
}

可以对此进行重构,以便在 lock 块内重新检查等待条件。这样您就不必释放并重新获取锁来执行检查。

object msg = null;
lock (MsgQueue) 
{
  while (MsgQueue.Count == 0) 
  {
    Monitor.Wait(MsgQueue);
  }
  msg = MsgQueue.Dequeue();
}

同样,因为我看到 continue 语句,所以我假设您知道在 Wait 之后必须始终重新检查等待条件。但是,以防万一您不知道此要求,我将在此说明,因为它很重要。

如果没有重新检查等待条件并且有 2 个或更多消费者,那么其中一个消费者可能会进入锁并使最后一项出列。即使另一个消费者通过调用PulsePulseAll从等待队列移动到就绪队列,这种情况仍然可能发生,但它没有机会重新获取第一个消费者之前的锁。显然,如果没有重新检查,消费者可能会尝试对空队列进行操作。生产端使用 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 a while loop wraps the lock which probably makes your code look more like the following.

object msg = null;
while (msg == null)
{
  lock (MsgQueue) 
  {
    if (MsgQueue.Count == 0) 
    {
      Monitor.Wait(MsgQueue);
      continue;
    }
    msg = MsgQueue.Dequeue();
  }
}

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.

object msg = null;
lock (MsgQueue) 
{
  while (MsgQueue.Count == 0) 
  {
    Monitor.Wait(MsgQueue);
  }
  msg = MsgQueue.Dequeue();
}

Again, because I see the continue statement I am assuming you are aware that the wait condition must always be rechecked after a Wait. 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 or PulseAll, 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 whether Pulse or PulseAll is used on the producing side. There is still a problem because the Monitor does not give preference to a Wait above an Enter.

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.

生生漫 2024-11-22 19:33:19

插入器线程在某些地方被阻塞,是的。

    lock ( MsgQueue ) {
        if ( MsgQueue.Count == 0 ) {  // LINE 1
            Monitor.Wait( MsgQueue ); // LINE 2
            continue;
        }
        msg = MsgQueue.Dequeue( ); // LINE 3
    }

在第 1 行,锁由读取器持有,因此插入器被阻止。

在第 2 行,锁被释放,并且不会重新获取,直到插入器可能在 MsgQueue 上调用 Monintor.Pulse

在第 3 行,锁仍然被持有(从第 1 行开始),之后由于退出 lock 范围而再次释放。

The inserter thread is being blocked at points, yes.

    lock ( MsgQueue ) {
        if ( MsgQueue.Count == 0 ) {  // LINE 1
            Monitor.Wait( MsgQueue ); // LINE 2
            continue;
        }
        msg = MsgQueue.Dequeue( ); // LINE 3
    }

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 on MsgQueue.

At line 3 the lock is still being held (from line 1), and afterwards it is released again due to exiting the lock scope.

っ〆星空下的拥抱 2024-11-22 19:33:19

如果插入器线程调用lock(MsgQueue),那么显然只要其中一个读者锁定了队列,它就会阻塞

If the inserter thread calls lock ( MsgQueue ) then obviously it will block whenever one of the readers has locked the queue

你是暖光i 2024-11-22 19:33:19

不,我认为您的问题是关于锁(MsgQueue)的含义,并且这个比喻可能有点误导。锁定对象不会以任何方式更改该对象的状态,也不会阻止其他线程,除非这些线程也在同一对象上使用lock

这就是为什么您经常看到这种(更好的)模式:

private Queue<MyClass> _queue = ...;
private object _queueLock = new object();
...
lock(_queueLock )
{
    _queue.Enqueue(item);
}

锁中使用的引用仅充当“门票”。

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 use lock on the same object too.

That's why you often see this (better) pattern:

private Queue<MyClass> _queue = ...;
private object _queueLock = new object();
...
lock(_queueLock )
{
    _queue.Enqueue(item);
}

The reference used in the lock only serves as a 'ticket'.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文