关于stl:C ++是否有生产就绪的无锁队列或哈希实现

Is there a production ready lock-free queue or hash implementation in C++

对于C ++中的无锁队列,我已经进行了大量的搜索。 我找到了一些代码和一些试验-但是没有什么我能编译的。 无锁哈希也将是受欢迎的。

摘要:
到目前为止,我还没有肯定的答案。
没有"生产就绪"库,令人惊讶的是,现有的库都不符合STL容器的API。


从1.53开始,boost提供了一组无锁的数据结构,包括队列,堆栈和单生产者/单消费者队列(即环形缓冲区)。


起点将是Herb Sutter的DDJ文章(针对单个生产者和消费者或多个)。他给出的代码(从每篇文章的第二页开始在线显示)使用C ++ 0x样式atomic < T >模板类型;您可以使用Boost进程间库进行模仿。

Boost代码被埋在进程间库的深处,但是已经通读了适当的头文件(atomic.hpp),以实现我熟悉的系统上必要的比较和交换操作的实现。


是!

我写了一个无锁队列。它具有功能?:

  • 完全无需等待(无CAS循环)
  • 超快(每秒超过一亿次入队/出队操作)
  • 使用C ++ 11 move语义
  • 根据需要增长(但仅在需要时增长)
  • 对元素进行无锁内存管理(使用预分配的连续块)
  • 独立(两个标头以及一个许可证和自述文件)
  • 在MSVC2010 +,Intel ICC 13和GCC 4.7.2下进行编译(并且应在任何C ++ 11完全兼容的编译器下工作)

它在GitHub上以简化的BSD许可提供(请随意分叉!)。

注意事项:

  • 仅用于单生产者单消费者体系结构(即两个线程)
  • 在x86(-64)上进行了彻底的测试,并且应该在ARM,PowerPC和其他CPU上工作,这些对齐的本机大小的整数以及指针加载和存储自然是原子的,但尚未在非x86 CPU上进行过现场测试(如果有人一个测试它让我知道)
  • 不知道是否侵犯了任何专利(使用后果自负,等等)。请注意,我是自己设计并实施的。


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

  • ProducerConsumerQueue,其中包含文档和示例代码。

  • AtomicHashMap,其中包含文档和示例代码

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

干杯!


有这样的库,但是在C中。

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

http://www.liblfds.org


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

公共git仓库


在检查了大多数给出的答案之后,我只能声明:

答案是不。

没有这样的事情可以直接使用。


我知道的最接近的东西是Windows互锁单链接列表。当然,仅Windows。


如果您具有多生产者/单消费者队列/ FIFO,则可以使用SLIST或琐碎的Free Lock LIFO堆栈轻松地制作一个LockFree。您要做的是为使用者使用第二个"私有"堆栈(为简单起见,也可以将其作为SLIST进行操作,也可以选择其他任何堆栈模型)。消费者从私有堆栈中弹出项目。每当私有LIFO被耗尽时,您执行Flush而不是弹出共享的并发SLIST(抓取整个SLIST链),然后按顺序将Flushed列表推入私有堆栈。

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

但是,它不适用于多消费者的情况(单生产者或多生产者)。

而且,就哈希表而言,它们是"条带化"的理想候选者,后者只是将哈希表划分为每个缓存段具有锁定的段。 Java并发库就是这样做的(使用32条纹)。如果您拥有轻巧的读写器锁,则可以同时访问哈希表以进行同时读取,并且只有在有争议的条带上进行写操作(并且可能允许您增加哈希表)时,您才会停止。

如果自己动手,请确保将您的锁与哈希条目交织在一起,而不是将所有锁放在彼此相邻的数组中,这样就不太可能出现错误共享。


我可能会晚一点。

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

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

也可以使用双字CAS在没有危险指针的情况下实现无锁队列(但是64位版本只能在使用cmpxchg16b的x86-64上实现),我在此发表了一篇博客文章(队列的未经测试的代码) :为x86 / x86-64实现通用双字比较和交换(LSE博客。)

我自己的基准向我展示了双锁队列(同样在Micheal&Scott 1996年的论文中)的性能与无锁队列一样好(我还没有达到足够的争论,所以锁数据结构存在性能问题,但是我的工作台太轻了现在)和Intel TBB的并发队列看起来更好(快了两倍)(相对于操作系统,在FreeBSD 9下,这是我到目前为止发现的最低限度,这个数字是8个线程)。 i7具有4个ht核心,因此具有8个逻辑CPU)的线程,并且具有非常奇怪的行为(我的简单基准测试的执行时间从几秒变为几小时!)

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


然后英特尔线程构建模块问世了。一时间,这很好。

PS:您正在寻找并发队列和并发哈希表


以下摘自Herb Sutter的关于并发无锁队列的文章http://www.drdobbs.com/parallel/writing-a-generalized-concurrent-queue/211601363?pgno=1。我进行了一些更改,例如编译器重新排序。人们需要GCC v4.4 +来编译此代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#include
#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;
}


据我所知,还没有公开可用的东西。实现者需要解决的一个问题是,您需要一个无锁的内存分配器,尽管我现在似乎找不到该链接,但它存在。


我写这篇文章的时间大概是在2010年,我相信在不同参考文献的帮助下。它是多生产者单一消费者。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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
    }

};

我发现了用c编写的另一种解决方案:

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