【发布时间】: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 因为我列出的权衡 - 使用原子保证消费者的读取将看到生产者的最新写入。在库实现中通常不能接受多次读取可能看不到最新写入的折衷
标签: c++ multithreading lock-free