【问题标题】:Concurrently Dequeueing an item同时出列一个项目
【发布时间】:2012-08-08 23:38:12
【问题描述】:

假设我们有两个线程使用下面执行的方法:

while(true){

    if(Queue.Count() <= 0){
        wait();
    }

    object anObject = Queue.Dequeue();
}

现在问题出现在 Queue 有一个元素 init 时,线程 1 即将执行 Queue.Count 行,线程 2 大约在 Queue.Dequeue() 上,执行优先级在线程 1 上。

当这种情况发生时,线程 1 将抛出异常,因为 Queue.Count() 将返回 1 并尝试从空队列中取出对象。我该如何处理?如果我想安全地出队,最好的解决方案是什么?我应该使用同步还是锁定某些东西?

最好的问候, 凯末尔

【问题讨论】:

标签: c# concurrency parallel-processing


【解决方案1】:

假设您使用的是 .NET 4.0 或更高版本并且确实需要队列,最好的解决方案是切换到使用 ConcurrentQueue 并且它是 TryDequeue 方法。 ConcurrentQueue 是线程安全的。

也就是说,从您的代码 sn-p 来看,您真正在寻找的是一个线程安全的生产者/消费者队列。在这种情况下,请使用 BlockingCollection 类,它是 Take 方法:

while(true){
    // This will block until an item becomes available to take.
    // It is also thread safe, and can be called by multiple 
    // threads simultaneously. When an item is added, only one
    // waiting thread will Take() it
    object anObject = myBlockingCollection.Take();

    // do something with anObject
}

【讨论】:

  • 在这种情况下使用BlockingCollection 时,您还可以使用GetConsumingEnumerable() 方法将整个循环替换为foreach 循环,这样读取效果更好,甚至还支持更好的结束条件。
【解决方案2】:

您可以使用线程安全队列ConcurrentQueue

或者如果你不想使用它

    while (true)
    {
        Monitor.Enter(lockObj);
        try
        {
            if (Queue.Count <= 0)
            {
                Monitor.Wait(lockObj);
            }

            object anObject = Queue.Dequeue();
        }
        finally
        {
            Monitor.Exit(lockObj);
        }
    }

或者如果使用lock

    while (true)
    {
        lock(lockObj)
        {
            if (Queue.Count <= 0)
            {
                Monitor.Wait(lockObj);
            }

            object anObject = Queue.Dequeue();
        }
    }

【讨论】:

  • 其实我正在尝试理解其背后的逻辑,是否有任何好的文档说明无论编程语言如何我们都可以做到这一点
【解决方案3】:

试试这个模式:

制片人

public void Produce(object o)
{
  lock (_queueLock)
  {
    _queue.Enqueue(o);
    Monitor.Pulse(_queueLock);
  }
}

消费者

public object Consume()
{
  lock (_queueLock)
  {
    while (_queue.Count==0)
    {
      Monitor.Wait(_queueLock);
    }
    return _queue.Dequeue();
  }
}

【讨论】:

    【解决方案4】:

    在访问之前锁定队列。

    lock (Queue) {
        // blah blah
    }
    

    编辑

    while(true){
        lock (Queue) {
            if (Queue.Count() > 0) {
                // Dequeue only if there is still something in the queue
                object anObject = Queue.Dequeue();
            }
        }     
    }
    

    【讨论】:

    • 在这种情况下使用锁可能是不合适的解决方案,因为我们将其锁定,如果 Queue.Count 返回 0,线程将开始等待发出信号,其他线程将等待,因为锁实现所以死锁会发生 ??! :)
    • @kkocabiyik:wait() 到底在做什么?为什么只有Queue.Count() &gt; 0 才出队?
    • Wait() 实际上代表调用信号量等待绑定到不同的线程。这样一个线程就会来回向其他线程发出信号。
    • 因此,据我所知,您已经完成了一种lock 实现。似乎只有一些if (Queue.Count() &gt; 0)在出队之前丢失了。
    • 除此之外,仅当 Queue.Count() > 0 时才出队不是我需要的。当您查看我的案例时,我试图了解如果线程 2 在第 4 行并且线程 1 在第 2 行并且轮到线程 1 会发生什么。轮到线程 1 时,Queue.Count() 会返回 1,线程 2 会执行 dequeue,最后线程 1 尝试将空队列出队
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-06-29
    • 2019-01-26
    • 2020-07-10
    • 1970-01-01
    • 2023-03-17
    • 2011-02-09
    • 1970-01-01
    相关资源
    最近更新 更多