源码路径
原理简析
申请一块连续内存数组作为队列,本地工作线程往队列尾部push和pop队列数据,其余竞争线程往队列首部steal队列数据,一首一尾,可以减少竞争。队列使用lock free技术实现
实现
类的数据成员:
butil::atomic<size_t> _bottom; //指向队列的尾部
size_t _capacity;//队列容量总大小
T* _buffer;//指向分配给队列的堆内存首地址
butil::atomic<size_t> BAIDU_CACHELINE_ALIGNMENT _top;//指向队列的头部
构造和初始化:
构造时将bottom和top变量都指向数组小标为【1】的位置,初始化时要让队列的初始化大小为2的整数倍。这两个的目的是:快速定位到访问元素并且支持循环访问。
push函数
// Push an item into the queue.
// Returns true on pushed.
// May run in parallel with steal().
// Never run in parallel with pop() or another push().
bool push(const T& x) {
const size_t b = _bottom.load(butil::memory_order_relaxed);
//steal会修改top值,这里用acquire是为了同步steal线程修改top值之前的修改。这里其实也可以不用acquire的,因为并没有其他需要同步的数据
const size_t t = _top.load(butil::memory_order_acquire);
if (b >= t + _capacity) { // Full queue.
return false;
}
//在同一时刻整个队列只有一个线程在做push操作,如果前面判断还有空间,那就一定还有空间了
_buffer[b & (_capacity - 1)] = x; //包含了一个取余操作,第一个push()调用是放在下标1那里
//这里用了memory_order_release保证别人在steal的时候如果看到了最新的bottom值,就能看到实际buffer里面的内容了
//bottom值只有push/pop自己这个线程会修改,其他线程steal时都只是读而已
_bottom.store(b + 1, butil::memory_order_release);
return true;
}
先获取bottom和top的值,如果队列已满则return false。否则这个队列就一定有空闲块,因为只有本线程会往队列里面塞元素。
pop函数
// Pop an item from the queue.
// Returns true on popped and the item is written to `val'.
// May run in parallel with steal().
// Never run in parallel with push() or another pop().
bool pop(T* val) {
const size_t b = _bottom.load(butil::memory_order_relaxed);
size_t t = _top.load(butil::memory_order_relaxed);
//t跟b指向同一个位置,此时是没有元素的,本线程也没有在push数据进去,所以肯定是没有元素了
if (t >= b) {
// fast check since we call pop() in each sched.
// Stale _top which is smaller should not enter this branch.
return false;
}
//newb就是我要pop的元素了,从底部开始
const size_t newb = b - 1;
//当我store之后,最多只有一个steal能看到我修改前的bottom值并返回true,其他都得回到循环来读取我这个store的新值
_bottom.store(newb, butil::memory_order_relaxed);//已经占用了一个位置,后来的steal会失败
butil::atomic_thread_fence(butil::memory_order_seq_cst);//保证t赋值完成后bottom的store已经全局可见了,怎么保证,因为他也阻止了storeload的重排
t = _top.load(butil::memory_order_relaxed);
//bottom在占用位置之前已经被一个steal占用了,return false(那个唯一的steal已经执行好了compare_exchange_strong抢了,修改了top的值)
if (t > newb) {
_bottom.store(b, butil::memory_order_relaxed);
return false;
}
*val = _buffer[newb & (_capacity - 1)];
//若相等则存在单个元素竞争的情况,若不等则至少有两个元素,最多又只有一个steal,所以肯定可以return true
if (t != newb) {
return true;
}
// Single last element, compete with steal()
//如果我拿到了这个元素,我把top后移一位而不是bottom前移一位
//如果我没有拿到这个元素,则不需要做什么,bottom还是要保留原来的值
const bool popped = _top.compare_exchange_strong(
t, t + 1, butil::memory_order_seq_cst, butil::memory_order_relaxed);
_bottom.store(b, butil::memory_order_relaxed);
return popped;
}
steal函数
// Steal one item from the queue.
// Returns true on stolen.
// May run in parallel with push() pop() or another steal().
bool steal(T* val) {
//acquire读bottom确保push写入的buffer元素的可见性
size_t t = _top.load(butil::memory_order_acquire);
size_t b = _bottom.load(butil::memory_order_acquire);
//t>=b的时候队列可能并不空,因为bottom 和top这种共享变量都是有同步延迟的。
if (t >= b) {
// Permit false negative for performance considerations.
return false;
}
//无锁队列?
do {
butil::atomic_thread_fence(butil::memory_order_seq_cst);//保证读bottom的值的时候top的值已经全局可见了?
b = _bottom.load(butil::memory_order_acquire);//bottom要拿到实际buffer的值所以都要用acquire
if (t >= b) {
return false;
}
*val = _buffer[t & (_capacity - 1)];
} while (!_top.compare_exchange_strong(t, t + 1,
butil::memory_order_seq_cst,//成功的内存序
butil::memory_order_relaxed));//失败的内存序
return true;
}
一个相关的issue:work_stealing_queue的原子屏障疑问?