繁体   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