c++ - 无锁有界堆栈 C++11 原子

标签 c++ multithreading c++11 stack lock-free

我正在考虑使用非常基本的有界(预分配)堆栈来以正确的 LIFO 顺序跟踪我的线程 ID。所以我想知道我的实现是否是线程安全的:

// we use maximum 8 workers
size_t idle_ids_stack[8];
// position current write happening at
std::atomic_uint_fast8_t idle_pos(0);

// this function is called by each thread when it is about to sleep
void register_idle(size_t thread_id) 
{
    std::atomic_thread_fence(std::memory_order_release);
    idle_ids_stack[idle_pos.fetch_add(1, std::memory_order_relaxed)] = thread_id;
}

// this function can be called from anywhere at anytime
void wakeup_one() 
{
    uint_fast8_t old_pos(idle_pos.load(std::memory_order_relaxed));
    std::atomic_thread_fence(std::memory_order_acquire);
    size_t id;
    do
    {
        if(old_pos == 0) return; // no idle threads in stack; exit;
        id = idle_ids_stack[old_pos-1];
    }
    while (!idle_pos.compare_exchange_weak(old_pos, old_pos-1, std::memory_order_acquire, std::memory_order_relaxed));
    // wakeup single thread
    signal_thread(id);
}

最佳答案

我不是无锁编程方面的专家,但我很确定您的代码不是线程安全的。

  1. 我们首先看一下register_idle():

    这里可能发生的情况是,Thread1 递增了 idle_pos,但在存储其 id 之前,另一个线程调用 wakeup_once 并使用了过时的 id(在最坏的情况下,甚至是无效的 id)上,因为数组尚未初始化)。我也不明白内存栅栏的原因。

  2. wakeup_one() 中,您有一个类似的问题(称为 ABA problem ):

    • 您加载当前的idle_pos并根据id
    • 另一个线程调用并完成wakeup_one(idle_pos 减少)。
    • 另一个线程调用 register_idle ,这会再次将idle_pos 增加到与之前相同的值。
    • 现在第一个线程恢复,认为 idle_pos 未更改并发出错误线程信号

我可能错了,但我相信通常不可能基于数组创建完全无锁的堆栈,因为您必须在单个原子操作中执行两件事:修改索引变量并存储或加载数组中的值。

除了这些逻辑错误之外,我强烈建议不要使用独立的内存栅栏(它们使代码的可读性较差,甚至可能成本更高)。另外,在确保程序与默认内存顺序正确之后,我才会开始手动指定内存顺序。

关于c++ - 无锁有界堆栈 C++11 原子,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30921411/

相关文章:

java - 当 Web 应用程序没有太多请求时如何启动计划线程?

python - 如何设置从 shell 调用时 python 脚本可以使用的最大线程

c++ - 是否可以定义一个 C++11 可变参数类模板,其可变参数基数取决于整数模板参数?

c++ - 移动指针类型的构造函数?

c++ - 如何在感兴趣的区域细化cgal中的网格

c++ - 将 CString 转换为 const char*

c++ - 复制目录内容

c++ - 比较两个整数数组并检查它们是否具有相等的值

c# - 我如何等待异步 ping 负载在 C# 中全部完成?

c++ - 类型特征优化