没有原子的 SPSC 无锁队列

Posted

技术标签:

【中文标题】没有原子的 SPSC 无锁队列【英文标题】:SPSC lock free queue without atomics 【发布时间】:2014-11-26 00:20:48 【问题描述】:

我的记录器有一个 SPSC 队列。

它肯定不是一个通用的 SPSC 无锁队列。

但是,考虑到关于如何使用、目标架构等的一系列假设,以及一些可接受的权衡,我将在下面详细介绍,我的问题基本上是,它是否安全/是否有效?

它将仅用于x86_64 架构,因此写入uint16_t 将是原子的。 只有生产者更新tail。 只有消费者更新head。 如果生产者读取 head 的旧值,则看起来队列中的空间比实际空间少,这在使用 is 的上下文中是可以接受的限制。 如果消费者读取旧值 tail,看起来队列中等待的数据比实际少,这也是可以接受的限制。

上述限制是可以接受的,因为:

消费者可能不会立即获得最新的tail,但最终会到达最新的tail,并会记录排队的数据。 生产者可能无法立即获得最新的head,因此队列看起来比实际更满。在我们的负载测试中,我们发现了我们记录的数量与队列的大小,以及记录器排空队列的速度,这个限制没有影响 - 队列中总是有空间。

最后一点,volatile 的使用是必要的,以防止每个线程只读取的变量被优化出来。

我的问题:

这个逻辑正确吗? 队列线程安全吗? volatile 够用吗? volatile 有必要吗?

我的队列:

class LogBuffer

public:
    bool is_empty() const  return head_ == tail_; 
    bool is_full()  const  return uint16_t(tail_ + 1) == head_; 
    LogLine& head()        return log_buffer_[head_]; 
    LogLine& tail()        return log_buffer_[tail_]; 
    void advance_head()    ++head_; 
    void advance_hail()    ++tail_; 
private:
    volatile uint16_t tail_ = 0;     // write position
    LogLine log_buffer_[0xffff + 1]; // relies on the uint16_t overflowing
    volatile uint16_t head_ = 0;     // read position
;

【问题讨论】:

应用常识:如果这已经足够了,为什么你不会看到许多无锁库以这种方式实现它们的 SPSC 队列 :) @sehe 因为我列出的权衡 - 使用原子保证消费者的读取将看到生产者的最新写入。在库实现中通常不能接受多次读取可能看不到最新写入的折衷 【参考方案1】:

这个逻辑正确吗?

是的。

队列线程安全吗?

没有。

volatile 是否足够? volatile 有必要吗?

不,两者都是。 Volatile 不是使任何变量线程安全的神奇关键字。您仍然需要为索引使用原子变量或内存屏障,以确保在生产或消费项目时内存排序正确。

更具体地说,在您为队列生产或使用项目后,您需要发出内存屏障以保证其他线程将看到更改。当您更新原子变量时,许多原子库会为您执行此操作。

顺便说一句,使用“was_empty”而不是“is_empty”来明确它的作用。此调用的结果是一个时间实例,当您对其值采取行动时,该实例可能已发生变化。

【讨论】:

鉴于我列出的权衡,在我描述的使用中,为什么它不安全? headtail 都只会提前,head 只会被消费者写入,tail 只会被生产者写入。对head 的写入在对tail 的写入之前重新排序只会使队列看起来不如实际情况那么满——记录器将记录的比当前排队的要少,但这是可以接受的,因为对tail 的写入最终将由被消费者看到。相反的重新排序将使生产者看到的队列空间比实际可用空间少,这也是可以接受的 好吧,很公平,但是在x86_64 上,对uint16_t 的写入将是原子操作 - 所以考虑到所有这些,它仍然不安全吗? @SteveLorimer x86_64 仅在 uint16_t 对齐时才保证原子性。您的代码中没有任何内容告诉编译器它不能将您的 int16 放在堆栈上的对齐边界上,这对堆的保证更弱。使用适当的原子语义并消除您和编译器的猜测。 谢谢 - 这正是我正在寻找的输入! 在我们的使用中,结构将是字对齐的 - 但你是对的,它应该被强制执行

以上是关于没有原子的 SPSC 无锁队列的主要内容,如果未能解决你的问题,请参考以下文章

原子操作实现无锁队列

无锁队列的实现(陈皓)

多线程编程之无锁队列

CAS无锁队列与线程同步

无锁队列的实现(陈皓)

无锁队列