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) // 如果是同一位置
{
if (enqueue_pos_.compare_exchange_weak(pos, pos + 1, std::memory_order_relaxed)) // compare函数作用:再次比较enqueue_pos_和pos,如果相同,将enqueue_pos_修改为pos+1,返回true;否则,说明enqueue_pos_已发生改变,其他线程已插入数据,将pos修改为enqueue_pos_,返回false。
break;
}
else if (dif < 0) // 队列溢出,保存的数据已达到max_size_
{
return false;
}
else
{
pos = enqueue_pos_.load(std::memory_order_relaxed);
}
}
cell->data_ = std::move(data); // 将数据保存下来
cell->sequence_.store(pos + 1, std::memory_order_release); // 将此cell的sequence设为自己的pos+1。
return true;
}
出队函数 bool dequeue(T& data)
- 获取出队位置
dequeue_pos_
- 获取
pos
处的buffer_
,即cell_
- 判断
pos + 1
是否等于cell_->sequence_
- 若相等,尝试占领
pos
这个位置(dequeue_pos_.compare_exchange_weak),让dequeue_pos_
加一,跳出循环 - 若
cell_->sequence_ < pos + 1
, // 队列为空,返回false - 若
cell_->sequence_ > pos + 1
,说明cell_
处数据已经出队,更新pos
,重新进入第2步
bool dequeue(T& data) // 传入的是引用,用以保存出队的数据
{
cell_t* cell;
size_t pos = dequeue_pos_.load(std::memory_order_relaxed); // 获取出队位置
for (;;)
{
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 + 1);
if (dif == 0) // 如果是同一位置
{
if (dequeue_pos_.compare_exchange_weak(pos, pos + 1, std::memory_order_relaxed)) // compare函数作用:再次比较dequeue_pos_和pos,如果相同,将dequeue_pos_修改为pos+1,返回true;否则,说明dequeue_pos_已发生改变,其他线程已删除数据,将pos修改为enqueue_pos_,返回false。
break;
}
else if (dif < 0) // 队列为空,返回false
return false;
else
pos = dequeue_pos_.load(std::memory_order_relaxed);
}
data = std::move(cell->data_);
cell->sequence_.store(pos + buffer_mask_ + 1, std::memory_order_release); // 将此cell的序号改为最后一个,即将此cell移动到队列最后面
return true;
}
队列大小 size_t approx_size()
此函数供外部调用,并没有在出队、入队函数中调用到,用以返回当前队列的实际长度。
size_t approx_size()
{
size_t first_pos = dequeue_pos_.load(std::memory_order_relaxed);
size_t last_pos = enqueue_pos_.load(std::memory_order_relaxed);
if (last_pos <= first_pos)
return 0;
auto size = last_pos - first_pos;
return size < max_size_ ? size : max_size_;
}
完整代码如下:
/*
A modified version of Bounded MPMC queue by Dmitry Vyukov.
Original code from:
http://www.1024cores.net/home/lock-free-algorithms/queues/bounded-mpmc-queue
licensed by Dmitry Vyukov under the terms below:
Simplified BSD license
Copyright (c) 2010-2011 Dmitry Vyukov. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of
conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list
of conditions and the following disclaimer in the documentation and/or other materials
provided with the distribution.
THIS SOFTWARE IS PROVIDED BY DMITRY VYUKOV "AS IS" AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL DMITRY VYUKOV OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The views and conclusions contained in the software and documentation are those of the authors and
should not be interpreted as representing official policies, either expressed or implied, of Dmitry Vyukov.
*/
/*
The code in its current form adds the license below:
Copyright(c) 2015 Gabi Melman.
Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "spdlog/common.h"
#include
#include
namespace spdlog
{
namespace details
{
template
class mpmc_bounded_queue
{
public:
using item_type = T;
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);
}
~mpmc_bounded_queue()
{
delete [] buffer_;
}
bool enqueue(T&& data)
{
cell_t* cell;
size_t pos = enqueue_pos_.load(std::memory_order_relaxed);
for (;;)
{
cell = &buffer_[pos & buffer_mask_];
size_t seq = cell->sequence_.load(std::memory_order_acquire);
intptr_t dif = (intptr_t)seq - (intptr_t)pos;
if (dif == 0)
{
if (enqueue_pos_.compare_exchange_weak(pos, pos + 1, std::memory_order_relaxed))
break;
}
else if (dif < 0)
{
return false;
}
else
{
pos = enqueue_pos_.load(std::memory_order_relaxed);
}
}
cell->data_ = std::move(data);
cell->sequence_.store(pos + 1, std::memory_order_release);
return true;
}
bool dequeue(T& data)
{
cell_t* cell;
size_t pos = dequeue_pos_.load(std::memory_order_relaxed);
for (;;)
{
cell = &buffer_[pos & buffer_mask_];
size_t seq =
cell->sequence_.load(std::memory_order_acquire);
intptr_t dif = (intptr_t)seq - (intptr_t)(pos + 1);
if (dif == 0)
{
if (dequeue_pos_.compare_exchange_weak(pos, pos + 1, std::memory_order_relaxed))
break;
}
else if (dif < 0)
return false;
else
pos = dequeue_pos_.load(std::memory_order_relaxed);
}
data = std::move(cell->data_);
cell->sequence_.store(pos + buffer_mask_ + 1, std::memory_order_release);
return true;
}
size_t approx_size()
{
size_t first_pos = dequeue_pos_.load(std::memory_order_relaxed);
size_t last_pos = enqueue_pos_.load(std::memory_order_relaxed);
if (last_pos <= first_pos)
return 0;
auto size = last_pos - first_pos;
return size < max_size_ ? size : max_size_;
}
private:
struct cell_t
{
std::atomic sequence_;
T data_;
};
size_t const max_size_;
static size_t const cacheline_size = 64;
typedef char cacheline_pad_t [cacheline_size];
cacheline_pad_t pad0_;
cell_t* const buffer_;
size_t const buffer_mask_;
cacheline_pad_t pad1_;
std::atomic enqueue_pos_;
cacheline_pad_t pad2_;
std::atomic dequeue_pos_;
cacheline_pad_t pad3_;
mpmc_bounded_queue(mpmc_bounded_queue const&) = delete;
void operator= (mpmc_bounded_queue const&) = delete;
};
} // ns details
} // ns spdlog