簡體   English   中英

為什么我的無鎖消息隊列是segfault :(?

[英]Why does my lock-free message queue segfault :(?

作為一個純粹的心理練習,我試圖讓它在沒有鎖或互斥體的情況下工作。 這個想法是當消費者線程正在讀取/執行消息時,它以原子方式交換生產者線程用於寫入的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();
}

如果我正確理解你的代碼,那就有數據競爭 ,例如:

// 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(...);

現在兩個線程同時訪問同一個隊列。 這是一場數據競賽 ,這意味着未定義的行為

您的無鎖隊列無法正常工作,因為您沒有從至少一個半正式的正確性證明開始,然后將該證明轉換為一個算法,其中證明是主要文本,注釋將證明連接到代碼,所有這些都與代碼。

除非你是復制/粘貼別人的落實誰這樣做,任何試圖寫一個無鎖算法將失敗。 如果您正在復制粘貼其他人的實施,請提供。

除非你有這樣的證據證明它們是正確的,否則無鎖算法並不健全,因為使它們失敗的錯誤類型是微妙的,必須格外小心。 簡單地“滾動”無鎖算法,即使它在測試期間未能導致明顯的問題,也是不可靠代碼的處方。

在這種情況下編寫正式證明的一種方法是追蹤已經證明正確的偽代碼等的人。 在評論中勾畫出偽代碼以及正確性證明。 然后填寫孔中的代碼。

一般來說,證明一個“幾乎正確”的無鎖算法存在缺陷比寫一個無鎖算法的正確證據更難,如果以特定方式實現,然后實現它。 現在,如果您的算法存在缺陷,很容易找到缺陷,那么您就不會對問題域有基本的了解。

簡而言之,通過發布“為什么我的算法錯誤”,您正在接近如何錯誤地編寫無鎖算法。 “我的證明中的缺陷在哪里?”,“我在這里證明了這個偽代碼是正確的,然后我實現了它,為什么我的測試顯示出死鎖?” 是很好的無鎖問題。 “這里有一堆帶有注釋的代碼,僅僅描述了下一行代碼的作用,沒有描述我為什么要編寫下一行代碼的注釋,或者這行代碼如何保持我的無鎖不變量”不是一個好的無鎖問題。

退后。 找到一些經過驗證的正確算法。 了解證明如何運作。 通過猴子實現一些經過驗證的正確算法 - 參見monkey-do。 查看腳注,注意他們的證據被忽略的問題(如AB問題)。 在您掌握了一堆之后,嘗試一個變體,並進行校對,檢查證明,並執行實施,並檢查實施情況。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM