brpc源码学习(三)- WorkStealingQueue

每个bthread_worker都有自己的work_steal_queue,bthread_worker会从自己queue里pop数据进行处理,如果自己的queue为空,则尝试去其他bthread_worker的queue中steal,所以当前queue不会发生pop和push并发的情况;可能发生并发的情况为,steal和steal,steal和push,steal和pop;为了避免锁的开销,brpc设计了lock-free的WorkStealingQueue。

 

work steal queue的push和pop在bottom侧,steal在top侧。

 

brpc源码学习(三)- WorkStealingQueue_第1张图片

首先看下push,因为steal不会修改bottom,所以74行bottom用relax读就可以保证准确,而top会被其他bthread_worker修改,因此通过acquire可以看到其他线程release修改top前对内存做的修改;然后判断是否超过queue的容量限制;如果没有超过容量限制,那么在bottom位置写入新数据,并更新bottom;80行bottom的写入使用release是为了保证steal能看到bottom处的数据写入。

 

然后看下pop

brpc源码学习(三)- WorkStealingQueue_第2张图片

relax得到的top是不精确的,这里只是为了快速判断是否为空。然后将bottom减一,为了保证同一元素不会既被pop,又被steal,因此需让bottom的这一修改立即全局可见,所以加了seq_cst的fence,同steal配对;然后获取top,如果top大于newb,说明queue空,此时需将bottom修改回去;然后将val设置为newb位置的数据;如果t != newb,说明队列中有不止一个数据,此时pop和steal不会竞争,因此直接返回;否则将产生竞争,如果此时top没有发生变化,即还等于t,那么说明此时没有发生steal,将top和bottom统一加一,pop的数据可用;如果top不等于t,那么说明发生了steal,此时需将bottom恢复,pop的数据不可用。这里cas里的seq_cst也是为了和steal的cas配对,否则将会导致132行错误的返回true。

 

brpc源码学习(三)- WorkStealingQueue_第3张图片

然后看下steal,为了能够看到push对buffer的修改,所以这里对bottom的读取使用了acquire,而top用acquire是为了看到pop里对内存的修改(虽然好像没有什么要同步的…)

然后是一个do while循环,126行的seq_cst是为了和pop的98行配对,保证能看到最新的bottom;127行对bottom的acquire读是为了若此时是空队列,然后126到127行之间push进来一个数据,如果不使用acquire可能会导致看到新的bottom,而没有看到新的数据的情况;然后设置val,cas读top,如果没有发生改变,说明此时没有steal和pop在和当前bthread_worker发生竞争,那么直接返回;如果top发生改变,说明发生了竞争,那么重新进入循环,这里cas成功时使用seq_cst是为了和steal,pop的cas配对。

 

brpc源码学习(三)- WorkStealingQueue_第4张图片

类成员定义中,bottom只会被当前线程修改,而top会被多线程修改,因此对top做了cacheline对齐以防止false sharing。

 

参考:

wxj大佬的博客

你可能感兴趣的:(计算机体系结构,brpc)