SPSC锁定没有原子的自由队列

时间:2014-11-26 00:20:49

标签: c++ multithreading lock-free

我的记录器下面有一个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
};

1 个答案:

答案 0 :(得分:1)

  

这个逻辑是否正确?

是。

  

队列线程安全吗?

没有

  

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

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

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

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