在C ++中是否有生产就绪的无锁队列或散列实现

时间:2009-07-22 09:08:29

标签: c++ stl lock-free

我一直在搜索C ++中的无锁队列。我发现了一些代码和一些试验 - 但我没有能够编译。也欢迎使用无锁哈希。

内容: 到目前为止,我没有正面答案。 没有“生产就绪”库,令人惊讶的是现有的库都没有符合STL容器的API。

15 个答案:

答案 0 :(得分:37)

从1.53开始,boost提供set of lock free data structures,包括队列,堆栈和单生产者/单用户队列(即环形缓冲区)。

答案 1 :(得分:25)

起点可能是Herb Sutter关于single producer and consumermultiple ones的DDJ文章。他给出的代码(从每篇文章的第二页开始在线)使用C ++ 0x样式原子< T>模板类型;您可以使用Boost进程间模型进行模拟。

增强代码隐藏在进程间库的深处,但是通过读取相应的头文件(atomic.hpp),我熟悉的系统上必要的比较和交换操作的实现看起来很合理。

答案 2 :(得分:15)

Facebook的Folly似乎有基于C ++ 11 <atomic>的无锁数据结构:

我敢说这些目前在生产中使用,所以我猜他们可以安全地用于其他项目。

干杯!

答案 3 :(得分:14)

是!

wrote a lock-free queue。它有Features™:

  • 完全等待(无CAS循环)
  • Super fast(每秒超过1亿次入队/出队行动)
  • 使用C ++ 11移动语义
  • 根据需要增长(但只有在你想要的时候)
  • 对元素进行无锁内存管理(使用预先分配的连续块)
  • 独立(两个标题加上许可证和自述文件)
  • 在MSVC2010 +,Intel ICC 13和GCC 4.7.2下编译(并且应该在任何C ++ 11完全兼容的编译器下工作)

根据简化的BSD许可证available on GitHub(随意分叉!)。

注意事项:

  • 仅适用于单一制作人单一消费者体系结构(即两个主题)
  • 在x86(-64)上进行了彻底的测试,并且应该在ARM,PowerPC和其他CPU上工作,其中对齐的本机大小的整数和指针加载和存储自然是原子的,但是没有在非x86 CPU上进行现场测试(如果有人有一个测试它让我知道)
  • 不知道是否侵犯了任何专利(使用风险等等)。请注意,我自己从头开始设计和实现它。

答案 4 :(得分:11)

有这样一个库,但它在C中。

包装到C ++应该很简单。

http://www.liblfds.org

答案 5 :(得分:10)

在检查了大部分给定答案后,我只能说明:

答案是

没有这样的东西可以直接使用。

答案 6 :(得分:10)

boost.lockfree试图创建lockfree堆栈和fifo类的c ++实现。

public git repository

答案 7 :(得分:6)

我最了解的是Windows Interlocked Singly Linked Lists。当然,它只是Windows。

答案 8 :(得分:5)

如果你有一个多生产者/单一消费者队列/ FIFO,你可以使用SLIST或一个简单的无锁LIFO堆栈轻松制作一个LockFree。你所做的是为消费者提供第二个“私人”堆栈(为简单起见,你也可以将其作为SLIST或你选择的任何其他堆栈模型)。消费者将物品从私人堆栈中弹出。每当私有LIFO被耗尽时,你会执行Flush而不是弹出共享并发SLIST(抓住整个SLIST链),然后按顺序遍历刷新列表将项目推送到私有堆栈。

适用于单一生产者/单一消费者和多生产者/单一消费者。

但是,它不适用于多个消费者案例(单一生产者或多生产者)。

此外,就哈希表而言,它们是“条带化”的理想候选者,它只是将哈希分成具有每个缓存段锁定的段。这就是Java并发库的工作方式(使用32条纹)。如果你有一个轻量级的读写器锁,可以同时访问哈希表以进行同步读取,并且只有在有争议的条带上发生写入时才会停止(并且可能如果你允许增加哈希表)。

如果您自己滚动,请确保将您的锁与哈希条目交错,而不是将所有锁定放在一个数组中,这样您就不太可能进行错误共享。

答案 9 :(得分:3)

然后Intel Threading Building Blocks来了。有一段时间,这很好。

PS:您正在寻找concurrent_queue和concurrent_hash_map

答案 10 :(得分:3)

我可能会迟到一点。

缺少解决方案(在提出问题时)主要是由于C ++中的一个重要问题(在C ++ 0x / 11之前):C ++没有(没有)并发内存模型。

现在,使用std :: atomic,您可以控制内存排序问题并进行适当的比较和交换操作。我自己编写了使用C ++ 11和Micheal的危险指针(IEEE TPDS 2004)实现Micheal&amp; Scott的无锁队列(PODC96),以避免早期的免费和ABA问题。它工作正常,但它是一个快速和肮脏的实现,我不满意实际的性能。代码可在bitbucket上获得:LockFreeExperiment

也可以使用双字CAS实现没有危险指针的无锁队列(但是只能使用cmpxchg16b在x86-64上使用64位版本),我有一篇关于它的博客文章(未经测试的代码用于在这里:Implementing generic double-word compare and swap for x86/x86-64(LSE博客。)

我自己的基准测试显示,双锁队列(也在Micheal&amp; Scott 1996论文中)的表现与无锁的一样(我没有达到足够的争用,因此锁定的数据结构存在性能问题,但我的对于现在来说,工作台太轻了,而且英特尔TBB的并发队列似乎更好(两倍快)对于一个相对较小的数量(取决于操作系统,在FreeBSD 9下,我到目前为止发现的最低界限,这个数字是一个i7上的8个线程,4个核心,因此是8个逻辑CPU)的线程,并且有非常奇怪的行为(我的简单基准测试的执行时间从几秒钟移动到几小时!)

遵循STL样式的无锁队列的另一个限制:在无锁队列上使用迭代器没有任何感觉。

答案 11 :(得分:1)

据我所知,目前还没有公开的东西。实现者需要解决的一个问题是你需要一个无锁的内存分配器,虽然我现在似乎无法找到链接。

答案 12 :(得分:1)

以下内容来自Herb Sutter关于并发锁定免费队列http://www.drdobbs.com/parallel/writing-a-generalized-concurrent-queue/211601363?pgno=1的文章。我做了一些改动,比如编译器重新排序的东西。一个人需要GCC v4.4 +来编译这段代码。

#include <atomic>
#include <iostream>
using namespace std;

//compile with g++ setting -std=c++0x

#define CACHE_LINE_SIZE 64

template <typename T>
struct LowLockQueue {
private:
    struct Node {
    Node( T* val ) : value(val), next(nullptr) { }
    T* value;
    atomic<Node*> next;
    char pad[CACHE_LINE_SIZE - sizeof(T*)- sizeof(atomic<Node*>)];
    };
    char pad0[CACHE_LINE_SIZE];

// for one consumer at a time
    Node* first;

    char pad1[CACHE_LINE_SIZE
          - sizeof(Node*)];

// shared among consumers
    atomic<bool> consumerLock;

    char pad2[CACHE_LINE_SIZE
          - sizeof(atomic<bool>)];

// for one producer at a time
    Node* last;

    char pad3[CACHE_LINE_SIZE
          - sizeof(Node*)];

// shared among producers
    atomic<bool> producerLock;

    char pad4[CACHE_LINE_SIZE
          - sizeof(atomic<bool>)];

public:
    LowLockQueue() {
    first = last = new Node( nullptr );
    producerLock = consumerLock = false;
    }
    ~LowLockQueue() {
    while( first != nullptr ) {      // release the list
        Node* tmp = first;
        first = tmp->next;
        delete tmp->value;       // no-op if null
        delete tmp;
    }
    }

    void Produce( const T& t ) {
    Node* tmp = new Node( new T(t) );
    asm volatile("" ::: "memory");                            // prevent compiler reordering
    while( producerLock.exchange(true) )
        { }   // acquire exclusivity
    last->next = tmp;         // publish to consumers
    last = tmp;             // swing last forward
    producerLock = false;       // release exclusivity
    }

    bool Consume( T& result ) {
    while( consumerLock.exchange(true) )
        { }    // acquire exclusivity
    Node* theFirst = first;
    Node* theNext = first-> next;
    if( theNext != nullptr ) {   // if queue is nonempty
        T* val = theNext->value;    // take it out
        asm volatile("" ::: "memory");                            // prevent compiler reordering
        theNext->value = nullptr;  // of the Node
        first = theNext;          // swing first forward
        consumerLock = false;             // release exclusivity
        result = *val;    // now copy it back
        delete val;       // clean up the value
        delete theFirst;      // and the old dummy
        return true;      // and report success
    }
    consumerLock = false;   // release exclusivity
    return false;                  // report queue was empty
    }
};

int main(int argc, char* argv[])
{
    //Instead of this Mambo Jambo one can use pthreads in Linux to test comprehensively
LowLockQueue<int> Q;
Q.Produce(2);
Q.Produce(6);

int a;
Q.Consume(a);
cout<< a << endl;
Q.Consume(a);
cout<< a << endl;

return 0;
}

答案 13 :(得分:0)

我找到了用c编写的另一个解决方案:

http://www.ddj.com/hpc-high-performance-computing/219500200

答案 14 :(得分:0)

我可能在2010年的某个时候写过这篇文章,我确信在不同参考资料的帮助下。它是多生产者单一消费者。

template <typename T>
class MPSCLockFreeQueue 
{
private:
    struct Node 
    {
        Node( T val ) : value(val), next(NULL) { }
        T value;
        Node* next;
    };
    Node * Head;               
    __declspec(align(4)) Node * InsertionPoint;  //__declspec(align(4)) forces 32bit alignment this must be changed for 64bit when appropriate.

public:
    MPSCLockFreeQueue() 
    {
        InsertionPoint = new Node( T() );
        Head = InsertionPoint;
    }
    ~MPSCLockFreeQueue() 
    {
        // release the list
        T result;
        while( Consume(result) ) 
        {   
            //The list should be cleaned up before the destructor is called as there is no way to know whether or not to delete the value.
            //So we just do our best.
        }
    }

    void Produce( const T& t ) 
    {
        Node * node = new Node(t);
        Node * oldInsertionPoint = (Node *) InterLockedxChange((volatile void **)&InsertionPoint,node);
        oldInsertionPoint->next = node;
    }

    bool Consume( T& result ) 
    {
        if (Head->next)
        {
            Node * oldHead = Head;
            Head = Head->next;
            delete oldHead;
            result = Head->value;
            return true;
        }       
        return false;               // else report empty
    }

};