为什么我的无锁消息队列是segfault :(?

时间:2014-03-11 22:54:41

标签: c++ multithreading c++11 atomic lock-free

作为一种纯粹的心理锻炼,我试图让它在没有锁或互斥体的情况下工作。这个想法是当消费者线程正在读取/执行消息时,它以原子方式交换生产者线程用于写入的std::vector。这可能吗?我试过玩线程围栏无济于事。这里有竞争条件,因为它偶尔会出现故障。我想它在enqueue函数的某个地方。有什么想法吗?

// should execute functions on the original thread
class message_queue {
public:
    using fn = std::function<void()>;
    using queue = std::vector<fn>;

    message_queue() : write_index(0) {
    }

    // should only be called from consumer thread
    void run () {
        // atomically gets the current pending queue and switches it with the other one
        // for example if we're writing to queues[0], we grab a reference to queue[0]
        // and tell the producer to write to queues[1]
        queue& active = queues[write_index.fetch_xor(1)];
        // skip if we don't have any messages
        if (active.size() == 0) return;
        // run all messages/callbacks
        for (auto fn : active) {
            fn();
        }
        // clear the active queue so it can be re-used
        active.clear();
        // swap active and pending threads
        write_index.fetch_xor(1);
    }
    void enqueue (fn value) {
        // loads the current pending queue and append some work
        queues[write_index.load()].push_back(value);
    }
private:
    queue queues[2];
    std::atomic<bool> is_empty; // unused for now
    std::atomic<int> write_index;


};
int main(int argc, const char * argv[])
{

    message_queue queue{};
    // flag to stop the message loop
    // doesn't actually need to be atomic because it's only read/wrote on the main thread
    std::atomic<bool> done(false);
    std::thread worker([&queue, &done] {
        int count = 100;
        // send 100 messages
        while (--count) {
            queue.enqueue([count] {
                // should be executed in the main thread
                std::cout << count << "\n";
            });
        }
        // finally tell the main thread we're done
        queue.enqueue([&] {
            std::cout << "done!\n";
            done = true;
        });
    });
    // run messages until the done flag is set
    while(!done) queue.run();
    worker.join();
}

2 个答案:

答案 0 :(得分:4)

如果我正确理解您的代码,则有数据竞赛,例如:

// producer
int r0 = write_index.load(); // r0 == 0

// consumer
int r1 = write_index.fetch_xor(1); // r1 == 0
queue& active = queues[r1];
active.size();

// producer
queue[r0].push_back(...);

现在两个线程同时访问同一个队列。这是数据竞赛,这意味着未定义的行为

答案 1 :(得分:3)

你的无锁队列无法工作,因为你没有至少从正式的半正式证明开始,然后将证明变成一个算法,证明是主要文本,注释将证据连接到代码,所有都与代码互连。

除非您复制/粘贴其他人这样做的实现,否则任何编写无锁算法的尝试都将失败。如果您正在复制粘贴其他人的实施,请提供。

锁定免费算法并不健全,除非您有这样的证据证明它们是正确的,因为使它们失败的错误类型是微妙的,必须格外小心。简单地“滚动”无锁算法,即使它在测试期间未能导致明显的问题,也是不可靠代码的一种方法。

在这种情况下编写正式证据的一种方法是追踪已经证明正确伪代码等的人。在评论中勾画出伪代码以及正确性证明。然后填写孔中的代码。

一般来说,证明一个“几乎正确”的无锁算法是有缺陷的,比写一个无锁算法如果以特定方式实现,然后实现它的可靠证据更难。现在,如果您的算法存在缺陷,以至于轻松找到缺陷,那么您就不会对问题域有基本的了解。

简而言之,通过发布“为什么我的算法错误”,您正在接近如何错误地编写无锁算法。 “我的证明中的缺陷在哪里?”,“我证明了这个伪代码正确在这里,然后我实现了它,为什么我的测试显示出死锁?”是很好的无锁问题。 “这里有一堆带有注释的代码,仅仅描述了下一行代码的作用,没有描述我为什么要编写下一行代码的注释,或者这行代码如何保持我的无锁不变量”不是一个好的无锁问题。

退一步。找到一些经过验证的正确算法。了解证明如何运作。通过猴子实现一些经过验证的正确算法 - 参见monkey-do。查看脚注,注意他们的证据被忽略的问题(如A-B问题)。在您掌握了一堆之后,尝试一个变体,并做证明,检查证明,并执行,并检查实现。