SPSC锁定免费队列,无原子



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

它当然不是通用的 SPSC 无锁队列。

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

  • 它只会在x86_64体系结构上使用,因此写入uint16_t将是原子的。
  • 只有制作者更新tail
  • 只有消费者更新head
  • 如果生产者读取旧值 head ,看起来队列中的空间比实际少,这在使用上下文中是可以接受的限制。
  • 如果消费者读取旧的值 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
};

这个逻辑正确吗?

是的。

队列线程安全吗?

不。

挥发性是否足够?挥发性是必要的吗?

不,两者都有。易失性不是一个使任何变量线程安全的神奇关键字。您仍然需要对索引使用原子变量或内存屏障,以确保在生成或使用项目时内存排序正确。

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

顺便说一句,使用"was_empty"而不是"is_empty"来明确它的作用。此调用的结果是一个实例,在您对其值执行操作时,该实例可能已更改。

最新更新