spdlog–mpmc_bounded_q.h
无锁队列分析
spdlog中调用了mpmc_bounded_q.h
无锁队列实现异步写日志。
构造函数
构造函数传入buffer最大值,并初始化了buffer数组buffer_
以及buffer_mask_
。
buffer_size
必须是2的幂次方。- 设置每个
buffer[i]
的值为其序号。 - 设置入队、出队位置为0。
mpmc_bounded_queue(size_t buffer_size)
:max_size_(buffer_size),
buffer_(new cell_t [buffer_size]),
buffer_mask_(buffer_size - 1)
{
//queue size must be power of two
if(!((buffer_size >= 2) && ((buffer_size & (buffer_size - 1)) == 0)))
throw spdlog_ex("async logger queue size must be power of two");
for (size_t i = 0; i != buffer_size; i += 1)
buffer_[i].sequence_.store(i, std::memory_order_relaxed);
enqueue_pos_.store(0, std::memory_order_relaxed);
dequeue_pos_.store(0, std::memory_order_relaxed);
}
入队函数 bool enqueue(T&& data)
- 获取插入的位置
pos = enqueue_pos_
- 获取
pos
处的buffer_
, 即cell_
- 判断
pos
是否等于cell_->sequence_
- 若相等,尝试占领
pos
这个位置(enqueue_pos_.compare_exchange_weak),让enqueue_pos_
加一,跳出循环 - 若
cell_->sequence_ < pos
, 队列中保存的数据已达到max_size_
,不入队 - 若
cell_->sequence_ > pos
,说明cell_
处已经被写入数据,更新pos
,重新进入第2步
疑问
cell->sequence_.store(pos + 1, std::memory_order_release);
,这里困扰我一阵,为什么要将cell的sequence设为pos+1?
个人见解
我觉得主要作用是标记pos处已经放置数据了。若其他线程获得相同的pos,当其再比较pos和sequence时将不会再相等,就不会再次在相同的pos处写入数据。另外,此处的pos+1和出队时的判断
intptr_t dif = (intptr_t)seq - (intptr_t)(pos + 1);
相对应。
bool enqueue(T&& data) // 传入待插入数据的右值引用
{
cell_t* cell;
size_t pos = enqueue_pos_.load(std::memory_order_relaxed); // 获取插入的位置
for (;;) //一直循环,直到enqueue_pos_.compare_exchange_weak返回true
{
cell = &buffer_[pos & buffer_mask_]; // 取出一个buffer_
size_t seq = cell->sequence_.load(std::memory_order_acquire); // 取出buffer_自己的序号
intptr_t dif = (intptr_t)seq - (intptr_t)pos;
if (dif == 0