.NET 线程 - 同步对象

本文关键字:对象 同步 线程 NET | 更新日期: 2023-09-27 17:59:40

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

一个线程插入队列,许多线程读取形成此队列。为了正确读取,读取器线程会像以下代码一样锁定队列。

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

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

.NET 线程 - 同步对象

另一个

线程将被锁(MsgQueue(阻塞,而这个线程在lock中,但在Monitor.Wait时不会被阻塞(释放锁,以便其他线程可以Pulse(。

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

更新:基于评论:

不,它简单地插入。机械臂没有锁

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

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

是的,当使用者持有锁时,生产者(或机械臂(将被阻止。请注意,锁由调用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没有优先考虑高于EnterWait

更新:

我忘了指出,如果您使用的是.NET 4.0,则可以利用BlockingCollection,它是阻塞队列的实现。它对多个生产者和使用者是安全的,如果队列为空,它会为您执行所有阻塞。

机械臂螺纹在某些点被阻塞,是的。

    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范围,它再次被释放。

如果机械臂线程调用lock ( MsgQueue )那么很明显,每当其中一个读卡器锁定队列时,它就会阻塞

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

这就是为什么你经常看到这个(更好的(模式:

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

锁中使用的引用仅用作"票证"。