【问题标题】:C# concurrent queue usageC#并发队列使用
【发布时间】:2020-07-21 04:51:13
【问题描述】:

有一个简单的问题。

如果一个线程正在排队而另一个正在出队,我是否必须使用并发队列?在这种情况下(1 位读者和 1 位作者)使用常规容器时是否存在竞争条件/其他风险?

【问题讨论】:

  • 是的,你必须这样做。这就是并发结构的用途——避免竞争条件。
  • 我在问,因为我知道当有多个写入器访问容器中的相同元素时,您必须使用这种结构,但是 1 个读取器和 1 个写入器的情况对我来说并不那么明显(那里是否有实际的比赛条件)
  • 对...我想不出来,但你可以写一个小程序并尝试一下。我认为您会得到的异常(使用常规队列)可能是队列大小/长度不一致......(现在不是通过计算机,所以无法尝试:/)
  • 我确实声明这两个操作都会改变查询。如果您想获取队列顶部的元素而不改变它,请使用 Peek 而不是 Dequeue

标签: c# concurrency thread-safety race-condition concurrent-queue


【解决方案1】:

使用ConcurrentQueue,您可以安全地从多个线程并行调用EnqueueTryDequeue 方法。这里没有竞争条件。您可以一整天每秒执行 1,000,000 次,没有任何问题(假设您不会在任何时候消耗所有可用内存)。但是,如果您想等待某个项目可用(如果没有),则可能存在竞争条件。例如,消费者线程可以像这样循环运行:

while (true)
{
    if (!queue.IsEmpty)
    {
        queue.TryDequeue(out var item); // Race condition!
        Process(item);
    }
    else
    {
        Thread.Sleep(50);
    }
}

此代码在对IsEmptyTryDequeue 的调用之间存在竞争条件。同时,队列可能被另一个线程清空。只需删除 IsEmpty 检查即可消除这种竞争条件:

while (true)
{
    if (queue.TryDequeue(out var item)) // Fixed
    {
        Process(item);
    }
    else
    {
        Thread.Sleep(50);
    }
}

虽然这样效率低下。该线程将执行非生产性循环,并且当一个项目可用时,它将在延迟后获取它。另请注意,队列无法通知线程它已完成,并且永远不会再有任何项目。这两个问题都可以通过专门的BlockingCollection 类解决。

foreach (var item in blockingCollection.GetConsumingEnumerable())
{
    Process(item);
}

GetConsumingEnumerable 方法可确保即时通知新项目或收集完成。

BlockingCollection 类有一个缺点。顾名思义,它在等待期间阻塞当前线程。如果这是您想避免的事情,您可以查看here 以快速了解异步替代方案。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-04-14
    • 2017-06-27
    • 2021-01-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-16
    • 2013-07-25
    相关资源
    最近更新 更多