【问题标题】:Multiple Producer Multiple Consumer lock-free (or even wait-free) queue多生产者多消费者无锁(甚至无等待)队列
【发布时间】:2011-08-30 00:21:37
【问题描述】:

我正在搜索有关如何将 MP/MC 队列编写为无锁甚至无等待的文档。我正在使用.Net 4.0。找了很多C++代码,但是我对内存模型不是很熟悉,所以移植到C#的时候很有可能会引入一些bug。

【问题讨论】:

标签: .net multithreading queue consumer producer


【解决方案1】:

为什么你认为你需要无锁队列?您是否尝试过使用ConcurrentQueue<T>,可能包含在BlockingCollection<T> 中?

编写多线程代码很难。编写无锁代码更加困难,除非你真的必须这样做,否则你不应该自己动手。

【讨论】:

  • 我正在编写通用子系统,所以我只想让它尽可能好。
  • @adontz,在这种情况下,你应该让你的系统可扩展:你提供了一些并发队列的实现,在大多数情况下已经足够好了,但如果他们需要的话,让你的用户自己编写。跨度>
  • 不幸的是(对我来说),主要用例是高负载,所以无锁只是“足够好”。我不希望默认实现无等待队列,但是基于锁的解决方案是不可接受的。我不是自己的敌人,确实需要至少实现一个无锁队列。
  • @adontz,你需要你的队列真的是先进先出吗?如果不是,并且您希望通常同一个线程既生产又消费项目,您可以使用ConcurrentBag<T>。无论如何,所有这些集合都试图尽可能少地使用锁定,所以我不确定你的解决方案会不会好很多。
【解决方案2】:

我的第一个选择是ConcurrentQueue<T>,但您可以将数据存储抽象到接口后面,以便轻松更改实现。然后对典型场景进行基准测试,看看你在哪里遇到问题。记住:过早的优化是万恶之源。设计您的系统,使其不依赖于实现,而是依赖于合同,然后您可以随心所欲地优化您的实现。

我使用 ILSpy 查看了 ConcurrentQueue<T>,乍一看似乎是一个无锁实现 - 很有可能它正是您正在寻找的。​​p>

【讨论】:

  • ConcurrentQueue 只是 MPMC 队列的一部分。它无助于等待新商品到货,通知新商品到货。我认为一个 AutoResetEvent 就足够了,它模拟了不同的情况:生产者在某些时候比消费者工作得快,消费者在某些时候比生产者工作得快。错误唤醒也是一个问题,线程不应该只是为了看到空队列而唤醒。所以有很多地方会出错。我一般都知道多线程,但绝对不是这方面的专业人士。
  • @adontz,这就是BlockingCollection<T> 可以帮助你的地方。如果您选择限制队列大小,它会为生产者等待消费者的新项目并等待队列中有可用空间。
【解决方案3】:

作为一个可供考虑的选项,有一个the bounded Multiple Producer Multiple Consumer queue by Dmitry Vyukov 算法。我已将该算法移植到 .NET,您可以找到 the sources on github。它非常快。

入队算法:

public bool TryEnqueue(object item)
{
    do
    {
        var buffer = _buffer; // prefetch the buffer pointer
        var pos = _enqueuePos; // fetch the current position where to enqueue the item
        var index = pos & _bufferMask; // precalculate the index in the buffer for that position
        var cell = buffer[index]; // fetch the cell by the index
        // If its sequence wasn't touched by other producers
        // and we can increment the enqueue position
        if (cell.Sequence == pos && Interlocked.CompareExchange(ref _enqueuePos, pos + 1, pos) == pos)
        {
            // write the item we want to enqueue
            Volatile.Write(ref buffer[index].Element, item);
            // bump the sequence
            buffer[index].Sequence = pos + 1;
            return true;
        }

        // If the queue is full we cannot enqueue and just return false
        if (cell.Sequence < pos)
        {
            return false;
        }

        // repeat the process if other producer managed to enqueue before us
    } while (true);
}

出队算法:

public bool TryDequeue(out object result)
{
    do
    {
        var buffer = _buffer; // prefetch the buffer pointer
        var bufferMask = _bufferMask; // prefetch the buffer mask
        var pos = _dequeuePos; // fetch the current position from where we can dequeue an item
        var index = pos & bufferMask; // precalculate the index in the buffer for that position
        var cell = buffer[index]; // fetch the cell by the index
        // If its sequence was changed by a producer and wasn't changed by other consumers
        // and we can increment the dequeue position
        if (cell.Sequence == pos + 1 && Interlocked.CompareExchange(ref _dequeuePos, pos + 1, pos) == pos)
        {
            // read the item
            result = Volatile.Read(ref cell.Element);
            // update for the next round of the buffer
            buffer[index] = new Cell(pos + bufferMask + 1, null);
            return true;
        }

        // If the queue is empty return false
        if (cell.Sequence < pos + 1)
        {
            result = default(object);
            return false;
        }

        // repeat the process if other consumer managed to dequeue before us
    } while (true);
}

【讨论】:

    猜你喜欢
    • 2011-02-11
    • 1970-01-01
    • 2014-10-31
    • 2012-01-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多