C ++锁定免费的生产者/消费者队列

时间:2011-10-26 23:02:36

标签: c++ lock-free

我在以下位置查看无锁队列的示例代码:

http://drdobbs.com/high-performance-computing/210604448?pgno=2

(也参考许多SO问题,例如Is there a production ready lock-free queue or hash implementation in C++

这看起来应该适用于单个生产者/消费者,尽管代码中存在许多拼写错误。我已经更新了代码,如下所示,但它正在崩溃我。有人有什么建议吗?

特别是,应该将divider和last声明为:

atomic<Node *> divider, last;    // shared

我没有在这台机器上支持C ++ 0x的编译器,所以也许这就是我所需要的......

// Implementation from http://drdobbs.com/high-performance-computing/210604448
// Note that the code in that article (10/26/11) is broken.
// The attempted fixed version is below.

template <typename T>
class LockFreeQueue {
private:
    struct Node {
        Node( T val ) : value(val), next(0) { }
        T value;
        Node* next;
    };
    Node *first,      // for producer only
    *divider, *last;    // shared
public:
    LockFreeQueue()
    {
        first = divider = last = new Node(T()); // add dummy separator
    }
    ~LockFreeQueue()
    {
        while( first != 0 )    // release the list
        {
            Node* tmp = first;
            first = tmp->next;
            delete tmp;
        }
    }
    void Produce( const T& t )
    {
        last->next = new Node(t);    // add the new item
        last = last->next;      // publish it

        while (first != divider) // trim unused nodes
        {
            Node* tmp = first;
            first = first->next;
            delete tmp;
        }
    }
    bool Consume( T& result )
    {
        if (divider != last)         // if queue is nonempty
        {
            result = divider->next->value; // C: copy it back
            divider = divider->next;      // D: publish that we took it
            return true;                  // and report success
        }
        return false;                   // else report empty
    }
};

我编写了以下代码来测试它。 Main(未显示)只调用TestQ()。

#include "LockFreeQueue.h"

const int numThreads = 1;
std::vector<LockFreeQueue<int> > q(numThreads);

void *Solver(void *whichID)
{
    int id = (long)whichID;
    printf("Thread %d initialized\n", id);
    int result = 0;
    do {
        if (q[id].Consume(result))
        {
            int y = 0;
            for (int x = 0; x < result; x++)
            { y++; }
            y = 0;
        }
    } while (result != -1);
    return 0;
}


void TestQ()
{
    std::vector<pthread_t> threads;
    for (int x = 0; x < numThreads; x++)
    {
        pthread_t thread;
        pthread_create(&thread, NULL, Solver, (void *)x);
        threads.push_back(thread);
    }
    for (int y = 0; y < 1000000; y++)
    {
        for (unsigned int x = 0; x < threads.size(); x++)
        {
            q[x].Produce(y);
        }
    }
    for (unsigned int x = 0; x < threads.size(); x++)
    {
        q[x].Produce(-1);
    }
    for (unsigned int x = 0; x < threads.size(); x++)
        pthread_join(threads[x], 0);    
}

更新:最终导致崩溃是由队列声明引起的:

std::vector<LockFreeQueue<int> > q(numThreads);

当我将其更改为一个简单的数组时,它运行正常。 (我实现了一个带锁的版本,它也崩溃了。)我看到析构函数在构造函数之后被立即调用,导致双重释放的内存。但是,有没有人知道为什么用std :: vector立即调用析构函数?

3 个答案:

答案 0 :(得分:1)

如你所知,你需要制作几个指针std :: atomic,你需要在循环中使用compare_exchange_weak来原子地更新它们。否则,多个消费者可能会使用相同的节点,而多个生产者可能会破坏列表。

答案 1 :(得分:1)

这些写入(只是代码中的一个示例)按顺序发生至关重要:

last->next = new Node(t);    // add the new item
last = last->next;      // publish it

C ++无法保证 - 优化器可以重新排列它喜欢的东西,只要当前线程总是充当 - 如果程序完全按照你编写它的方式运行。然后CPU缓存可以进一步重新排序。

你需要记忆围栏。使指针使用原子类型应该具有这种效果。

答案 2 :(得分:0)

这可能完全不合适,但我不禁想知道你是否有某种与静态初始化相关的问题......对于笑,尝试将q声明为指针到无锁队列的向量,并在main()的堆上分配它。