bthread源码分析(三)基础数据结构和组件

本文介绍了bthread中的四种核心数据结构:WorkStealingQueue、RemoteTaskQueue、RingBuffer和ParkingLot。WorkStealingQueue实现了一种无锁队列,支持push、pop和steal操作。RemoteTaskQueue用于非工作线程提交任务,内置锁用于同步。RingBuffer作为全局调度队列,通常能够实现无等待。ParkingLot用于管理线程的睡眠与唤醒。
部署运行你感兴趣的模型镜像

1、WorkStealingQueue

WorkStealingQueue是一种无锁队列,用做bthread中的任务队列。WorkStealingQueue提供三种操作:push,pop,steal。由于push和pop都是在本线程中完成的,因此push和pop操作不会并发,两个push和两个pop之间也不会并发,但是steal是从其他线程发起的,因此会和push或pop并发。

源码位置:bthread/work_stealing_queue.h

基本操作

队列保存头尾指针_top和_bottom,push操作在_bottom侧执行,pop操作在_bottom侧执行,steal操作在_top侧执行,如图示

并发正确性分析

push vs steal

push中

1、set _bottom = _bottom + 1

steal中

2、see new _bottom

3、set _top = _top + 1

push和steal并发的正确性很容易推理,要么steal没看到_bottom的新值返回失败,要么steal看到了_bottom的新值,从_top侧拿到数据。_bottom使用acq-rel语义保证数据内容对steal的可见性

pop vs steal

pop中

1、set _bottom = _bottom - 1

2、see _top == _bottom

3、try to set _top = _top + 1

steal中

4、see _top == _bottom - 1

5、try to set _top = _top + 1

顺序:

4,1,2,3,5:pop成功拿到数据,steal在(5)失败

4,1,2,5,3:steal成功拿到数据,pop在(3)失败

其他情况均会有一个操作提前失败无法执行到最后一步,因此正确性得到保障。

2、RemoteTaskQueue

基于base::BoundedQueue实现的有容量限制的任务队列,用于非工作线程提交任务。在brpc中,每个工作线程维护一个RemoteTaskQueue,非工作线程没有自己的任务队列,因此非工作线程需要通过工作线程提交任务。由于非工作线程随机选择一个工作线程的RemoteTaskQueue提交任务,本身就降低了访问同一个RemoteTaskQueue的冲突,因此RemoteTaskQueue的实现比较简单,内置一把锁用于同步,支持push和pop两种操作。

源码地址:bthread/remote_task_queue.h

3、RingBuffer

RingBuffer是环型数组,用做bthread的全局调度队列,一般情况下wait-free

数据结构

RingBuffer是一段连续空间的用户类型T的数组,到达尾部后下个槽位返回头部,因此逻辑上是环型的,数组长度是2的幂。RingBuffer通过引入version的概念实现了wait-free,这里以数组长度为4举例

概念解释:

index:RingBuffer维护递增的index,从0开始,标记下一次进行操作的槽位,图中并未显示。push和pop操作维护各自的index,即一共有两个index,next_push_index和next_pop_index。当next_pop_index更大,说明有消费者需要等待,此时消费者在该槽位睡眠等待next_push_index追上后唤醒

物理槽位号:物理槽位就是数组下标,这里共4个槽位,槽位号从0~3。index >> _slot_bits得到槽位号,这里4个槽位_slot_bits就是2(1 << 2 == 4)

round:index & _slot_version(图中4个槽位就是0x03),图中所示为进行了4个round后的情况

version:每个round version会发生2次变化。上一个round的终止version是2 * round,本轮push操作完成后,version变为2 * round + 1,pop操作完成后,version变为2 * round + 2,也是下个round的起始version

wait-free条件

RingBuffer在一般情况下是wait-free的,换句话说RingBuffer的wait-free需要一定条件,对于push操作,如果当前队列未满,那么push是wait-free的;对于pop操作,如果当前队列有待消费的数据,那么pop操作是wait-free的。这里分开解释下wait-free的原因。

对于push操作,对_next_push_index执行fetch_add操作获取全局唯一index,于是这个push操作的(槽位,round)是唯一的。由于队列未满,此槽位的上一轮pop一定已经完成了,当前version == 2 * round,push操作执行完成,不会发生阻塞,因此是wait-free的。

对于pop操作,对_next_pop_index执行fetch_add获取全局唯一index,这个pop操作的(槽位,round)是唯一的。由于队列中有待消费数据,因此上一轮push一定已经完成了,当前version == 2 *round + 1,pop操作执行完成,不会发生阻塞,因此是wait-free的。

对于队列满的情况,说明消费能力不足,此时push操作只能spin等待别无他法;对于队列空的情况,消费者在自己获取的槽位上睡眠等待唤醒。

4、ParkingLot

ParkingLot即睡眠点,当bthread的工作线程偷完一遍发现没有任务可偷时,会在睡眠点进入睡眠等待唤醒。重启点数量是在大量无效唤醒和无法及时唤醒之间做的权衡,当前bthread共有4个重启点,每次需要唤醒时将一个唤醒点的线程唤醒。ParkingLot实现较简单,每个睡眠点在一个futex上等待。源码位置:bthread/parking_log.h

您可能感兴趣的与本文相关的镜像

Anything-LLM

Anything-LLM

AI应用

AnythingLLM是一个全栈应用程序,可以使用商用或开源的LLM/嵌入器/语义向量数据库模型,帮助用户在本地或云端搭建个性化的聊天机器人系统,且无需复杂设置

<think>好的,用户想了解如何在C++中使用bthread进行并发编程。首先,我需要回忆一下bthread的基本概念。bthread是百度开源的BRPC框架中的用户态线程库,类似于协程,用于高并发场景。用户可能已经知道多线程,但想用更轻量级的并发方式。 接下来,我需要说明使用bthread的基本步骤。首先,应该包括必要的头文件,比如<bthread/bthread.h>。然后,编写任务函数,这个函数的签名需要符合void* (*)(void*)的形式。然后,使用bthread_start_background或bthread_start_urgent来启动bthread。这些函数类似于pthread_create,但管理的是用户态线程。 然后,可能需要提到如何编译链接程序,需要链接brpc库,使用-lbrpc。另外,bthread的启动函数参数可能需要传递数据,可以通过结构体封装数据,并在任务函数中释放,避免内存泄漏。 还要注意bthread的同步机制,比如bthread的join,或者使用互斥锁条件变量。例如,bthread_join可以等待bthread结束。同时,bthread的同步原语可能与pthread的不同,需要特别说明。 可能还需要提到bthread普通线程的区别,比如bthread由调度器管理,可以在多个pthread之间调度,减少上下文切换开销。此外,bthread的栈大小可以调整,适合高并发。 用户可能需要一个示例代码来展示如何启动bthread,传递参数,以及等待其完成。在示例中,结构体的使用资源的释放是关键点,避免内存泄漏。例如,定义一个结构体包含需要传递的参数,在任务函数中转换为该结构体,处理完后delete。 还需要注意错误处理,检查bthread_start_background的返回值,确保bthread创建成功。另外,bthread的函数可能返回错误码,需要进行适当的处理。 最后,要总结使用bthread的优势,比如高并发、低开销,以及注意事项,如避免阻塞操作,合理管理资源。然后根据这些内容,提出相关问题,帮助用户进一步理解bthread与其他并发机制的区别、同步方法、与pthread的对比,以及适用场景。</think><< 在BRPC框架中使用bthread进行并发编程时,主要通过以下步骤实现: 1. 包含必要头文件: ```cpp #include <bthread/bthread.h> #include <butil/logging.h> ``` 2. 编写任务函数: ```cpp void* task_func(void* arg) { int* value = static_cast<int*>(arg); LOG(INFO) << "bthread processing value: " << *value; delete value; // 清理动态分配的内存 return nullptr; } ``` 3. 启动bthread: ```cpp int main() { for (int i = 0; i < 5; ++i) { int* arg = new int(i); // 动态分配参数内存 bthread_t tid; if (bthread_start_background(&tid, nullptr, task_func, arg) != 0) { LOG(ERROR) << "Failed to create bthread"; delete arg; } } // 等待所有bthread完成(示例) while (bthread_getconcurrency() > 1) { usleep(10000); } return 0; } ``` 实现原理注意事项: 1. 用户态线程:bthread是M:N线程模型,由BRPC的调度器管理,比操作系统线程更轻量 2. 非抢占式调度:bthread主动让出CPU时才会切换,通常通过bthread_usleep等阻塞操作触发 3. 栈管理:每个bthread有独立栈空间(默认1MB),可通过brpc::Controller调整 4. 资源管理:参数需要动态分配并在任务中释放,避免悬垂指针 5. 同步机制:使用bthread::Mutex、bthread::ConditionVariable等配套同步原语 编译命令示例: ```bash g++ -std=c++11 example.cpp -lbrpc -lpthread ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值