Lock Free Bounded Stack C ++ 11 atomics

时间:2015-06-18 16:57:37

标签: 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 个答案:

答案 0 :(得分:3)

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

  1. 让我们先来看register_idle()

    这里可能发生的是Thread1递增idle_pos但在它存储其id之前,另一个线程调用wakeup_once并使用过时的id(在最坏的情况下甚至是无效的on,因为数组是尚未初始化)。此外,我还没有看到记忆围栏的原因。

  2. wakeup_one()中您遇到类似问题(称为ABA problem):

    • 根据idle_pos加载当前id
    • 另一个线程调用并完成wakeup_one(idle_pos减少)。
    • 又一个线程调用register_idle,再次将idle_pos增加到与以前相同的值。
    • 现在第一个线程恢复,认为idle_pos未更改并发出错误的线程
  3. 我可能错了,但我相信通常不可能基于数组创建完全无锁堆栈,因为你必须在一个原子操作中做两件事:修改索引变量并存储或加载数组中的值。

    除了那些逻辑错误之外,我强烈建议不要使用独立的内存防护(它们使代码的可读性降低,甚至可能更昂贵)。此外,在确保程序与默认程序一致后,我才会开始手动指定内存顺序。