【中间件】bthread效率为什么高?
bthread效率为什么更高?
1 基本概念
bthread是brpc中的用户态线程,也是协程的一种实现。其采用M:N模型,即多个用户线程映射到少量的系统线程上。
2 高效做法
- 用户态调度:避免内核态和用户态之间的切换开销,上下文切换更快。系统线程的切换需要内核接入,而用户态线程的切换完全在用户空间完成,减少了系统调用和上下文切换的开销。
- 更轻量级的上下文切换:用户态线程的上下文数据量风小,只需要保存必要的寄存器状态,而内核线程需要保存更多的状态信息,比如浮点寄存器、信号处理器等。
- M:N模型:多个用户线程由较少的系统线程调度,减少了系统线程的创建和销毁开销,同时也减少了上下文切换的次数。系统线程的数量通常与cpu核心数相当,避免了过多的线程竞争。
- 无锁或细粒度锁的数据结构:任务队列使用无锁队列或细粒度锁,减少了线程间的竞争和等待时间,提高了并发性能。
- 工作窃取(work stealing):当某个工作线程的任务队列为空时,可以从其他线程的队列中窃取任务,实现负载均衡,避免线程空闲,提高资源利用率。
- 定制化的内存池管理:采用内存池技术,复用栈空间,减少内存分配和释放的开销,避免频繁的系统调用。
- 避免阻塞系统调用:通过异步IO或非阻塞IO配合事件驱动,减少了线程因IO操作而阻塞的情况,提高了CPU利用率。
进一步解释
- 用户态调度
避免内核陷入;
能够实现0系统调用(无需内核调度器);
类型 | 上下文切换时长 | 操作 |
---|---|---|
用户态 | 50 - 100 ns | 仅需保存/恢复必要的寄存器(约10个reg) |
内核态 | 1-5 us | 保存完整的上下文(浮点寄存器、信号处理器等);切换内核态堆栈 |
- M:N模型
维度 | M:N模型 | 1:1模型(eg. pthread) |
---|---|---|
线程数量 | 百万级用户线程 | 千级系统线程 |
调度开销 | 用户态协作式调度 | 内核抢占式调度 |
内存占用 | 每个线程约4-64KB栈 | 每个线程约2-10MB |
创建/销毁成本 | 微秒级(纯用户态操作) | 毫秒级(需内核参与) |
- 任务调度策略
- 工作窃取算法
Task *steal_task()
{for (Worker &w : other_workers) {if (Task *t = w.queue.try_steal()) {return t;}}return nullptr;
}// 每个worker线程维护本地任务队列
// 空闲worker从其他worker的队列尾部窃取任务
// 减少锁竞争,提高CPU缓存命中率
- 协作式调度
显式yield让出cpu;
避免不必要的抢占,减少上下文切换;
- 内存管理优化
- 栈内存复用
class StackPool {
public:static constexpr int MAX_CACHED_STACKS = 1000;std::vector<void*> cached_stacks;void *alloc() {if (!cached_stacks.empty()) {return pop_back();}return ::malloc(STACK_SIZE);}void free(void *stack) {if (cached_stacks.size() < MAX_CACHED_STACKS) {cached_tasks.push_back(stack);} else {::free(stack);}}
};// 减少频繁的malloc/free
// 避免内存碎片
** 个人疑问?**
栈内存复用的场景是什么?
- 与异步I/O深度集成
- 事件驱动架构
void async_read(int fd, void *buf, size_t size) {epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, ...);bthread_yield();// IO完成后由事件循环唤醒
}// 通过epoll/kqueue实现非阻塞IO
// IO等待期间自动yield,不阻塞worker线程
3 性能对比数据
参考网络数据,本人未验证。
场景 | bthread吞吐量 | pthread吞吐量 |
---|---|---|
10k空循环任务 | 1.2M tasks/sec | 120K tasks/sec |
网络代理(1KB包) | 850K req/sec | 65K req/sec |
数据库访问 | 720K QPS | 45K QPS |
4 bthread适用场景
- 高并发网络服务(eg. web服务器、rpc框架)
- 大规模并行计算(eg. 分布式任务调度)
- 低延迟交易系统(eg. 金融订单处理)
- 资源受限环境(eg. 嵌入式设备)
5 代价与限制
-
开发复杂度高
eg. 需要手动处理yield点 -
无法利用多核并行
单个worker线程仍绑定单个cpu核心 -
调试困难
用户态线程的堆栈跟踪不如内核线程直观
6 汇总原因
bthread的高效源自现代多核硬件和网络服务特征的深度优化,通过减少不必要的内核交互、精细化资源管理和智能调度策略,在特定场景下可带来数量级的性能提升。