多生产者多个消费者无锁(甚至无等待)队列

时间:2011-05-20 22:45:36

标签: .net multithreading queue consumer producer

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

3 个答案:

答案 0 :(得分:2)

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

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

答案 1 :(得分:2)

作为一个考虑的选项,有一个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);
}

答案 2 :(得分:1)

我的第一步是使用ConcurrentQueue<T>,但您可以将数据存储抽象出接口,这样您就可以轻松更改实现。然后对典型场景进行基准测试,看看遇到问题的地方。记住:过早的优化是万恶之源。设计您的系统,使其不依赖于实现,而是与合同相关联,然后您可以根据需要优化实施。

我看了ConcurrentQueue<T>和ILSpy,乍一看似乎是一个无锁的实现 - 很有可能它正是你正在寻找的。