【中间件】bthread效率为什么高?

bthread效率为什么更高?

1 基本概念

bthread是brpc中的用户态线程(也可称为M:N线程库),目的是:提高程序的并发度,同时降低编码难度,在多核cpu上提供更好的scalability和cache locality。其采用M:N模型,即多个用户线程(bthread)映射到少量的系统线程(pthread)上。
linux当下的pthread实现(NPTL)是1:1的,M个bthread也相当于映射至N个LWP。
bthread前身是Distributed Process(DP)中的fiber,一个N:1的合作式线程库,等价于event-loop库,但是同步方式。

2 高效做法

  1. 用户态调度:避免内核态和用户态之间的切换开销,上下文切换更快。系统线程的切换需要内核接入,而用户态线程的切换完全在用户空间完成,减少了系统调用和上下文切换的开销。
  2. 更轻量级的上下文切换:用户态线程的上下文数据量风小,只需要保存必要的寄存器状态,而内核线程需要保存更多的状态信息,比如浮点寄存器、信号处理器等。
  3. M:N模型:多个用户线程由较少的系统线程调度,减少了系统线程的创建和销毁开销,同时也减少了上下文切换的次数。系统线程的数量通常与cpu核心数相当,避免了过多的线程竞争。
  4. 无锁或细粒度锁的数据结构:任务队列使用无锁队列或细粒度锁,减少了线程间的竞争和等待时间,提高了并发性能。
  5. 工作窃取(work stealing):当某个工作线程的任务队列为空时,可以从其他线程的队列中窃取任务,实现负载均衡,避免线程空闲,提高资源利用率。
  6. 定制化的内存池管理:采用内存池技术,复用栈空间,减少内存分配和释放的开销,避免频繁的系统调用。
  7. 避免阻塞系统调用:通过异步IO或非阻塞IO配合事件驱动,减少了线程因IO操作而阻塞的情况,提高了CPU利用率。

进一步解释

  1. 用户态调度
    避免内核陷入;
    能够实现0系统调用(无需内核调度器);
类型上下文切换时长操作
用户态50 - 100 ns仅需保存/恢复必要的寄存器(约10个reg)
内核态1-5 us保存完整的上下文(浮点寄存器、信号处理器等);切换内核态堆栈
  1. M:N模型
维度M:N模型1:1模型(eg. pthread)
线程数量百万级用户线程千级系统线程
调度开销用户态协作式调度内核抢占式调度
内存占用每个线程约4-64KB栈每个线程约2-10MB
创建/销毁成本微秒级(纯用户态操作)毫秒级(需内核参与)
  1. 任务调度策略
  • 工作窃取算法
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;
    避免不必要的抢占,减少上下文切换;
  1. 内存管理优化
  • 栈内存复用
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
// 避免内存碎片

** 个人疑问?**
栈内存复用的场景是什么?

  1. 与异步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/sec120K tasks/sec
网络代理(1KB包)850K req/sec65K req/sec
数据库访问720K QPS45K QPS

4 bthread适用场景

  1. 高并发网络服务(eg. web服务器、rpc框架)
  2. 大规模并行计算(eg. 分布式任务调度)
  3. 低延迟交易系统(eg. 金融订单处理)
  4. 资源受限环境(eg. 嵌入式设备)

5 代价与限制

  • 开发复杂度高
    eg. 需要手动处理yield点

  • 无法利用多核并行
    单个worker线程仍绑定单个cpu核心

  • 调试困难
    用户态线程的堆栈跟踪不如内核线程直观

6 汇总原因

bthread的高效源自现代多核硬件和网络服务特征的深度优化,通过减少不必要的内核交互、精细化资源管理和智能调度策略,在特定场景下可带来数量级的性能提升。

7 FAQ

7.1 bthread不是coroutine

我们常说的协程特指N:1线程库,即所有的协程运行于一个系统线程中,计算能力和各类eventloop库等价。由于不跨线程,协程之间的切换不需要系统调用,可以非常快(100ns-200ns),受cache一致性的影响也小。但代价是协程无法高效地利用多核,代码必须非阻塞,否则所有的协程都被卡住,对开发者要求苛刻。协程的这个特点使其适合写运行时间确定的IO服务器,典型如http server,在一些精心调试的场景中,可以达到非常高的吞吐。但百度内大部分在线服务的运行时间并不确定,且很多检索由几十人合作完成,一个缓慢的函数会卡住所有的协程。在这点上eventloop是类似的,一个回调卡住整个loop就卡住了,比如ubaserver(注意那个a,不是ubserver)是百度对异步框架的尝试,由多个并行的eventloop组成,真实表现糟糕:回调里打日志慢一些,访问redis卡顿,计算重一点,等待中的其他请求就会大量超时。所以这个框架从未流行起来。

bthread是一个M:N线程库,一个bthread被卡住不会影响其他bthread。关键技术两点:work stealing调度和butex,前者让bthread更快地被调度到更多的核心上,后者让bthread和pthread可以相互等待和唤醒。这两点协程都不需要。

7.2 最好不要在用户程序中调用bthread

除非你需要在一次RPC过程中让一些代码并发运行,你不应该直接调用bthread函数,把这些留给brpc做更好。

7.3 bthread和pthread woker

pthread worker在任何时间只会运行一个bthread,当前bthread挂起时,pthread worker先尝试从本地runqueue弹出一个待运行的bthread,若没有,则随机偷另一个worker的待运行bthread,仍然没有才睡眠并会在有新的待运行bthread时被唤醒。

7.4 bthread中能调用阻塞的pthread或系统函数

只阻塞当前pthread worker。其他pthread worker不受影响

若bthread因bthread API而阻塞,它会把当前pthread worker让给其他bthread。若bthread因pthread API或系统函数而阻塞,当前pthread worker上待运行的bthread会被其他空闲的pthread worker偷过去运行

7.5 pthread可以调用bthread API

bthread API在bthread中被调用时影响的是当前bthread,在pthread中被调用时影响的是当前pthread。使用bthread API的代码可以直接运行在pthread中。

7.6 若有大量的bthread调用了阻塞的pthread或系统函数,会影响RPC运行

比如有8个pthread worker,当有8个bthread都调用了系统usleep()后,处理网络收发的RPC代码就暂时无法运行了。只要阻塞时间不太长, 这一般没什么影响, 毕竟worker都用完了, 除了排队也没有什么好方法. 在brpc中用户可以选择调大worker数来缓解问题, 在server端可设置ServerOptions.num_threads或-bthread_concurrency, 在client端可设置-bthread_concurrency.

规避方法

  • 一个容易想到的方法是动态增加worker数. 但实际未必如意, 当大量的worker同时被阻塞时, 它们很可能在等待同一个资源(比如同一把锁), 增加worker可能只是增加了更多的等待者.
  • 那区分io线程和worker线程? io线程专门处理收发, worker线程调用用户逻辑, 即使worker线程全部阻塞也不会影响io线程. 但增加一层处理环节(io线程)并不能缓解拥塞, 如果worker线程全部卡住, 程序仍然会卡住, 只是卡的地方从socket缓冲转移到了io线程和worker线程之间的消息队列. 换句话说, 在worker卡住时, 还在运行的io线程做的可能是无用功. 事实上, 这正是上面提到的没什么影响真正的含义. 另一个问题是每个请求都要从io线程跳转至worker线程, 增加了一次上下文切换, 在机器繁忙时, 切换都有一定概率无法被及时调度, 会导致更多的延时长尾.
  • 一个实际的解决方法是限制最大并发, 只要同时被处理的请求数低于worker数, 自然可以规避掉"所有worker被阻塞"的情况.
  • 另一个解决方法当被阻塞的worker超过阈值时(比如8个中的6个), 就不在原地调用用户代码了, 而是扔到一个独立的线程池中运行. 这样即使用户代码全部阻塞, 也总能保留几个worker处理rpc的收发. 不过目前bthread模式并没有这个机制, 但类似的机制在打开pthread模式时已经被实现了. 那像上面提到的, 这个机制是不是在用户代码都阻塞时也在做"无用功"呢? 可能是的. 但这个机制更多是为了规避在一些极端情况下的死锁, 比如所有的用户代码都lock在一个pthread mutex上, 并且这个mutex需要在某个RPC回调中unlock, 如果所有的worker都被阻塞, 那么就没有线程来处理RPC回调了, 整个程序就死锁了. 虽然绝大部分的RPC实现都有这个潜在问题, 但实际出现频率似乎很低, 只要养成不在锁内做RPC的好习惯, 这是完全可以规避的.

7.7 bthread没有channel

channel代表的是两点间的关系,而很多现实问题是多点的,这个时候使用channel最自然的解决方案就是:有一个角色负责操作某件事情或某个资源,其他线程都通过channel向这个角色发号施令。如果我们在程序中设置N个角色,让它们各司其职,那么程序就能分类有序地运转下去。所以使用channel的潜台词就是把程序划分为不同的角色。channel固然直观,但是有代价:额外的上下文切换。做成任何事情都得等到被调用处被调度,处理,回复,调用处才能继续。这个再怎么优化,再怎么尊重cache locality,也是有明显开销的。另外一个现实是:用channel的代码也不好写。由于业务一致性的限制,一些资源往往被绑定在一起,所以一个角色很可能身兼数职,但它做一件事情时便无法做另一件事情,而事情又有优先级。各种打断、跳出、继续形成的最终代码异常复杂。

我们需要的往往是buffered channel,扮演的是队列和有序执行的作用,bthread提供了ExecutionQueue,可以完成这个目的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/81209.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

DeepSeek V2:引入MLA机制与指令对齐

长上下文革命:Multi-Head Latent Attention(MLA)机制 传统 Transformer 的多头注意力需要缓存所有输入token的 Key 和 Value,这对长文本推理时的内存开销极为庞大。DeepSeek V2 针对这一难题提出了“Multi-Head Latent Attention”(MLA)机制。MLA 的核心思想是对多头注意…

Druid监控sql导致的内存溢出--内存分析工具MemoryAnalyzer(mat)

问题 druid监控sql在网页端显示&#xff0c;我的服务插入sql比较大&#xff0c;druid把执行过的sql保存在DruidDataSource类的成员变量JdbcDataSourceStat dataSourceStat&#xff1b; JdbcDataSourceStat类中的LinkedHashMap<String, JdbcSqlStat> sqlStatMap中&#…

《Python实战进阶》No45:性能分析工具 cProfile 与 line_profiler

Python实战进阶 No45&#xff1a;性能分析工具 cProfile 与 line_profiler 摘要 在AI模型开发中&#xff0c;代码性能直接影响训练效率和资源消耗。本节通过cProfile和line_profiler工具&#xff0c;实战演示如何定位Python代码中的性能瓶颈&#xff0c;并结合NumPy向量化操作…

计算机操作系统知识集合

主要来自小林coding 硬件结构 cpu位宽 如果用 32 位 CPU 去加和两个 64 位大小的数字&#xff0c;就需要把这 2 个 64 位的数字分成 2 个低位 32 位数字和 2 个高位 32 位数字来计算&#xff0c;先加个两个低位的 32 位数字&#xff0c;算出进位&#xff0c;然后加和两个高位…

电机常用易混淆概念说明(伺服、舵机、多轮)

1. 概述 基础动力需求 &#xff1a;普通电机&#xff08;如水泵、风扇&#xff09;。 高精度控制 &#xff1a;优先伺服系统或伺服电机&#xff08;如数控机床&#xff09;。 微型化场景 &#xff1a;舵机&#xff08;如遥控模型&#xff09;。 移动底盘 &#xff1a;单舵轮成…

进程与线程:04 内核线程

内核级线程概述 上一讲我们学习了用户级线程&#xff0c;了解了其切换和创建方式。用户级线程切换核心在于从一个栈变为两个栈&#xff0c;每个线程有自己的栈和线程控制块&#xff08;tcb&#xff09;&#xff0c;切换时先切换tcb再切换栈&#xff0c;创建时将切换的pc指针放…

信息系统项目管理师-软考高级(软考高项)​​​​​​​​​​​2025最新(六)

个人笔记整理---仅供参考 第六章项目管理概论 6.1PMBOK的发展 6.2项目基本要素 组织过程资产指的是项目上的&#xff0c;国产数据库的使用----安保和安全指的是环境因素 6.3项目经理的角色 6.4价值驱动的项目管理知识体系

[蓝桥杯 2023 国 Python B] 划分 Java

import java.util.*;public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);int[] arr new int[41];int sum 0;for (int i 1; i < 40; i) {arr[i] sc.nextInt();sum arr[i];}sc.close();int target sum / 2; // 最接近的两…

Redis05-进阶-主从

零、文章目录 Redis05-进阶-主从 1、搭建主从架构 &#xff08;1&#xff09;概述 单节点Redis的并发能力是有上限的&#xff0c;要进一步提高Redis的并发能力&#xff0c;就需要搭建主从集群&#xff0c;实现读写分离。 &#xff08;2&#xff09;集群概况 我们搭建的主从…

小结:ipsec-ike

IPSec 手动配置与自动配置&#xff08;IKE动态协商&#xff09; 手动配置IPSec 逻辑图 #mermaid-svg-eNMnNEwnoTjF8fkV {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-eNMnNEwnoTjF8fkV .error-icon{fill:#552222;}…

潇洒郎: 100% 成功搭建Docker私有镜像仓库并管理、删除镜像

1、Registry Web管理界面 2、拉取Registry-Web镜像 创建配置文件 tee /opt/zwx-registry/web-config.yml <<-EOF registry:url: http://172.28.73.90:8010/v2name: registryreadonly: falseauth:enabled: false EOF 拉取docker-registry-web镜像并绑定Registry仓库 …

《机器学习中的过拟合与模型复杂性:理解与应对策略》

《机器学习中的过拟合与模型复杂性&#xff1a;理解与应对策略》 摘要 在机器学习中&#xff0c;过拟合是模型在训练数据上表现良好但在新数据上泛化能力差的现象。本文深入探讨了过拟合与模型复杂性之间的关系&#xff0c;分析了复杂模型导致过拟合的原因&#xff0c;并介绍…

linux中sigint和sigterm的区别

SIGINT 和 SIGTERM 是在 Unix 及类 Unix 系统&#xff08;包括 Linux&#xff09;中用于进程间通信的信号&#xff0c;它们都可以用于请求进程终止&#xff0c;区别如下&#xff1a; 1、信号编号与定义 在信号机制里&#xff0c;每个信号都有对应的编号&#xff0c;这便于系统…

一套SaaS ERP管理系统源码,支持项目二开商用,SpringBoot+Vue+ElementUI+UniAPP

ERP管理系统源码&#xff0c;一款适用于小微企业的SaaS ERP管理系统源码, 采用最新的技术栈开发(SpringBootVueElementUIUniAPP)&#xff0c;让企业简单上云。 专注于小微企业的应用需求&#xff0c;如企业基本的进销存、询价&#xff0c;报价, 采购、销售、MRP生产制造、品质…

2025 新生 DL-FWI 培训

摘要: 本贴给出 8 次讨论式培训的提纲, 每次培训 1 小时. 1. Basic concepts 主动学习: 提问, 理解, 继续追问. 通过不断迭代, 逐步提升问题的质量, 加深理解. 1.1 Seismic exploration 问 DeepSeek (下同): 为什么进行地震勘探? 问: 地震勘探一般的深度是多少? 1.2 Sesmi…

mac电脑pytest生成测试报告

时隔了好久再写代码&#xff0c;感觉我之前的积累都白费了&#xff0c;全部忘记了&#xff0c;看来每一步都有记录对于我来说才是最好的。 最近又要重新搞接口自动化&#xff0c;然而是在mac电脑&#xff0c;对于我长期使用windows的人来说真的是个考验&#xff0c;对此次过程…

神经辐射场(NeRF)技术解析:3D重建与虚拟世界的未来

神经辐射场&#xff08;NeRF&#xff09;技术解析&#xff1a;3D重建与虚拟世界的未来 ——从算法突破到元宇宙基础设施的演进之路 摘要 本文通过算法演进图谱、训练流程解析、PyTorch代码实战及产业应用洞察&#xff0c;构建从学术创新到工程落地的完整技术框架。实验数据显…

ES搜索知识

GET /categories/1/10?name手机 // 按名称过滤 GET /categories/1/10?type电子产品 // 按类型过滤 GET /categories/1/10?name手机&type电子产品 // 组合过滤 查询参数 ApiOperation(value "获取商品分类分页列表")GetMapping("{page}/{limit}")…

【Docker】Docker拉取部分常用中间件

一、拉取MySQL 这里以Docker拉取MySQL5.7为例 #拉取镜像 docker pull mysql:5.7 docker run -d --name oj-mysql -p 3306:3306 -e "TZAsia/Shanghai" -e "MYSQL_ROOT_PASSWORD123456" mysql:5.7 -e 参数用于设置容器内的环境变量。TZ 是用于设置时区的环…

在 Ubuntu 上离线安装 ClickHouse

在 Ubuntu 上离线安装 ClickHouse 的步骤如下: 一.安装验证 # 检查服务状态 sudo systemctl status clickhouse-server #删除默认文件 sudo rm /etc/clickhouse-server/users.d/default-password.xml # 使用客户端连接 clickhouse-client --password