C# 并发队列的使用方式

3

我有一个快速的问题。

如果一个线程在入队,另一个线程在出队,我是否必须使用并发队列?在这种情况下(1个读者和1个写者),使用常规容器是否存在竞态条件或其他风险?


1
是的,你必须这样做。这就是并发结构的作用 - 避免竞态条件。 - undefined
我之所以问这个问题,是因为我知道当有多个写入者访问容器中的同一个元素时,必须使用这样的结构。然而,对于只有1个读取者和1个写入者的情况,对我来说并不那么明显(是否存在实际的竞争条件)。 - undefined
1
对啊...我现在脑子里想不起来了,但你可以写一个小程序试试看。我觉得(使用普通队列)你会遇到的异常可能是队列大小/长度不一致之类的问题...(现在没电脑所以不能试一下 :/ ) - undefined
1
我需要说明的是,这两个操作都会改变查询。如果你想获取队列顶部的元素而不改变它,请使用Peek而不是Dequeue - undefined
1个回答

5

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

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 类有一个缺点。正如其名称所示,它会在等待期间阻塞当前线程。如果您想避免这种情况,可以在这里查看异步替代方案的快速摘要。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接