我的记录器下面有一个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
};
答案 0 :(得分:1)
这个逻辑是否正确?
是。
队列线程安全吗?
没有
挥发性是否足够?有必要挥发吗?
不,两者兼而有之。 Volatile不是一个神奇的关键字,使任何变量线程安全。您仍然需要为索引使用原子变量或内存屏障,以确保在生成或使用项目时内存排序正确。
更具体地说,在为队列生成或使用项目之后,需要发出内存屏障以保证其他线程将看到更改。更新原子变量时,许多原子库都会为您执行此操作。
顺便说一下,使用" was_empty"而不是" is_empty"要明确它的作用。此调用的结果是一个时间实例,在您对其值执行操作时可能已更改。