簡體   English   中英

為什么程序只能在調試模式下工作?

[英]Why does program only work with debugging mode?

我試圖了解無鎖編程,並寫了一個無鎖堆棧:

template <typename T>
class LockFreeStack
{
    struct Node
    {
        std::shared_ptr<T> data;
        Node* next;

        explicit Node(const T& _data)
            : data(std::make_shared<T>(_data)), next(nullptr)
        {}
    };
    std::atomic<Node*> head;

public:
    void push(const T& data)
    {
        auto n{new Node(data)};
        n->next = head.load();
        while (!head.compare_exchange_weak(n->next, n))
            ;
    }

    std::shared_ptr<T> pop(void)
    {
        auto old_head{head.load()};
        while (old_head && head.compare_exchange_weak(old_head, old_head->next))
            ;
        return old_head ? old_head->data : std::shared_ptr<T>{};
    }
};

以及兩個用於推送/彈出操作的線程:

static LockFreeStack<int> global_stack;

main功能:

int main(void)
{
    std::srand(std::time(nullptr));

    std::thread pushing_thread([](void) {
        for (size_t i{}; i < MAX_LENGTH; ++i)
        {
            const auto v{std::rand() % 10000};
            global_stack.push(v);

            std::cout << "\e[41mPoping: " << v << "\e[m" << std::endl;
        }
    });
    std::thread poping_thread([](void) {
        for (size_t i{}; i < MAX_LENGTH; ++i)
        {
            if (auto v{global_stack.pop()}; v)
            {
                std::cout << "\e[42mPushing: " << *v << "\e[m" << std::endl;
            }
        }
    });

    pushing_thread.join();
    poping_thread.join();
}

該程序僅在調試模式下運行pushing_thread ,但是當我使用調試器運行該程序時,它會按預期運行兩個線程,或者如果我在線程之間等待片刻:

std::thread pushing_thread(...);
std::this_thread::sleep_for(1s);
std::thread poping_thread(...);

它工作正常。 那么當我們用調試器運行程序時會發生什么呢?

  • 編譯器: GCC 9.3
  • 編譯器標志: -std=c++2a -lpthread -Wall
  • 操作系統: ArchLinux with linux-5.5.13

這個邏輯有問題:

    while (old_head && head.compare_exchange_weak(old_head, old_head->next))
        ;

如果 old_head 不為空並且交換成功,則再試一次!

您的實現存在所謂的 ABA 問題。 考慮以下場景:

  • 堆棧看起來像這樣: A->B->C (即頭指向 A)
  • 線程 1 加載頭部(即A的地址)和A的下一個指針( B ),但在它可以執行 CAS 之前,它被中斷了。
  • 線程 2 彈出A ,然后B ,然后壓入A ,即堆棧現在看起來像這樣: A->C
  • 線程 1 恢復並愉快地將頭從A更新到B -> 您的堆棧已損壞 - 看起來像這樣: B->C - 但B當前正在被線程 2 使用。

有幾種可能的解決方案可以避免 ABA 問題,例如標記指針或並發內存回收方案。

更新:
標記指針只是一個用版本計數器擴展的指針,每次更新指針時版本標記都會增加。 您可以使用 DWCAS (Double-Width-CAS) 來更新具有單獨版本字段的結構,或者您可以將版本標記壓縮到指針的高位。 並非所有架構都提供 DWCAS 指令(x86 提供),如果高位未使用,則取決於操作系統(在 Windows 和 Linux 上,通常可以使用最高 16 位)。

關於內存回收方案的主題,我可以向您推薦我的論文: C++ 中無鎖數據結構的有效內存回收

暫無
暫無

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

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