新闻详情

新闻详情

首页 / 资讯中心 / 详情

brpc 线程模型全景解析:从 reactor 到 M:N 调度与 bthread 实践

发布时间:2026/9/14 8:38:06来源:尧图网络
brpc 线程模型全景解析:从 reactor 到 M:N 调度与 bthread 实践
brpc 线程模型全景解析从 reactor 到 M:N 调度与 bthread 实践【免费下载链接】brpcbrpc is an Industrial-grade RPC framework using C Language, which is often used in high performance system such as Search, Storage, Machine learning, Advertisement, Recommendation etc. brpc means better RPC.项目地址: https://gitcode.com/GitHub_Trending/brpc/brpcbrpc 是使用 C 编写的工业级 RPC 框架其核心并发底座是一套名为 bthread 的 M:N 线程库。本文以 threading_overview.md 为骨架系统梳理从连接独占线程到单线程 reactorN:1 线程库多线程 reactorM:N 线程库的完整线程模型谱系并结合 brpc 仓库中 bthread 的调度器TaskControl、TaskGroup、无锁任务队列WorkStealingQueue与同步原语butex源码说明各模型在多核扩展性与异步编程上的固有问题以及 brpc 如何通过保持同步编码风格 M:N 调度给出工程化解答。读完本文你将理解各类线程模型的适用场景、brpc 选择 M:N 架构的动机以及如何在业务代码中正确使用 bthread。线程模型光谱五种典型形态在讨论更好的线程模型之前先建立完整坐标系。brpc 官方文档将常见并发模型归纳为五种形态它们之间并非互相取代的简单演进而是在编码心智负担与多核利用率之间取不同平衡点。连接独占线程或进程C10K 问题的根源最朴素的做法是一个线程/进程独占一条连接在该连接关闭前线程既不退出也不做其他事情连接上的所有消息都由它串行处理。这个模型的正确性最直观——没有并发就没有竞争但它有一个致命的规模瓶颈当连接数增多时线程/进程占用的资源每个线程独立的栈、内核调度实体、文件描述符等和上下文切换成本急剧上升服务器性能迅速劣化。这正是历史上著名的 C10K 问题 的来源。该方法常见于早期 Web 服务器如经典的 fork-per-connection 模型如今已很少使用。它本质上是用资源堆积换逻辑简单在连接规模受限的场景下尚可一旦并发连接上千资源消耗与调度开销便不可承受。单线程 reactor一个核上的事件循环以 libevent、libev 等 event-loop 库为典型。这个模型通常由一个 event dispatcher 等待各类事件事件发生后原地in-place调用对应的 event handler全部处理完后再继续等待更多事件如此往复形成loop。本质把多段逻辑按事件触发顺序交织interleave在一个系统线程中执行——回调之间并不是真的并发而是分时复用同一条执行流。由此可以推出它的能力边界一个 event-loop 只能使用一个 CPU 核。因此这类程序要么是 IO-bound要么每个 handler 的运行时间确定且较短例如 HTTP 服务器否则一个耗时漫长的回调就会卡住整个程序产生高延迟对应图中不可控! 除非是专有服务的红色警示。不适合多人协作开发。只要一个人往回调里塞入阻塞代码如磁盘 IO、同步锁就会拖慢所有其他代码的响应整个团队的产出质量被最差的一个人绑定。竞争条件相对简单。由于 event handler 不会同时运行callback 之间天然互斥一些场景下甚至不需要锁。扩展主要靠多进程。单线程模型无法利用多核实践中通常部署多个进程、由前端负载均衡切分流量。N:1 线程库Fiber用上下文切换替代回调跳转N:1 线程库又称 Fiber协程典型实现有 GNU Pth、StateThreads。模型把 N 个用户线程映射进一个系统线程同一时刻只有一个用户线程在运行且采用协作式调度只有当前用户线程调用阻塞原语时才切换到其他用户线程。从能力上讲N:1 线程库与单线程 reactor等价——都是单执行流上的多路复用区别仅在于实现手段事件回调被替换为上下文栈、寄存器、signal mask运行回调变成了跳转上下文。因此它的优缺点与 event-loop 库高度相似单个 N:1 线程库无法充分发挥多核性能只适合特定程序如确定性运行时间的 IO 服务器优点是对 CPU cache 友好——只有一个系统线程若能舍弃对 signal mask 的支持用户线程间的上下文切换可以做到很快约 100~200ns因为切换纯在用户态完成、不涉及内核 syscall扩展性同样主要靠多进程部署。多线程 reactor事件驱动的自然扩展以 boost::asio 为典型。由一个或多个线程分别运行 event dispatcher事件发生后把 event handler 交给某个worker 线程执行通常经由一个任务队列投递对应图中标红 Highly contended! 的 Dispatcher 角色。相比单线程 reactor多线程 reactor 的优势明显天然利用多核是单线程 reactor 的直观扩展worker 间负载均衡更及时。由于共享地址空间线程间交互传递任务指针成本很低worker 线程之间可以频繁均衡负载而多进程方案基本依赖更前端的服务来切分流量。一个设计良好的多线程 reactor 往往能比同一台机器上的多个单线程 reactor 进程更均匀地使用不同核心。但它也有硬伤这正是 threading_overview.md 强调的重点受 cache 一致性限制无法获得线性于核心数的扩展。详见 atomic_instructions.md 中的 cacheline 讨论。在特定场景中粗糙的多线程 reactor 实现跑在 24 核上甚至不如精致的单线程 reactor 跑在 1 个核上快对应图中 Cache bouncing! 的警示worker 从 Dispatcher 拿走任务时相关 cacheline 需要在核心间同步。回调不要求非阻塞。由于存在多个 worker 线程单个 event handler 阻塞未必会延缓其他 handler——除非所有 worker 都被阻塞才会影响整体进展。事实上大部分 RPC 框架都采用这个模型且回调中常有阻塞部分例如同步等待访问下游 RPC 的返回。M:N 线程库调度灵活性与实现难度的博弈M:N 线程库把 M 个用户线程映射进 N 个系统线程M 通常远大于 N。相比多线程 reactorM:N 线程库可以自行决定一段代码何时开始、在哪运行、何时结束在调度上具备更多灵活度。不过实现全功能的 M:N 线程库是困难的至今仍是活跃的研究话题。本文语境下的 M:N 线程库特指面向网络服务的实现此时部分需求可以简化不需要时间片抢占不需要完备的优先级。实现路径有两种用户态实现以新语言为主如 GHC threads 和 goroutine。这类语言可以围绕线程库设计全新的关键字如 go 语句并拦截所有相关 API。现有语言实现往往需要修改操作系统内核例如 Windows UMS 和 Google SwitchTo后者虽是 1:1但可基于它实现 M:N 的效果。在使用体验上M:N 线程库与系统线程更接近——用户代码可同步编程但也需要像系统线程那样用锁或消息传递保证线程安全这与 N:1 线程库天然单线程的简单性不同。模型背后的两大难题无论选择哪种模型最终都要直面两个根本问题多核扩展性与异步编程的复杂度。brpc 文档对它们的剖析直接决定了 bthread 的设计取向。难题一多核扩展性理论上若所有代码都以纯事件驱动方式编写reactor 模型的能力可以最大化。但现实中由于编码难度和可维护性用户的使用方式大都是混合的回调中往往会发起同步操作阻塞住 worker 线程使其无法处理其他请求。串行依赖放大调度压力一个请求往往要经过几十个服务线程把大量时间花在等待下游请求上。用户不得不开几百个线程以维持足够吞吐这造成了高强度的调度开销并降低了 TLS线程局部存储相关代码的效率——因为线程数越多TLS 内存的浪费与切换损耗越明显。全局队列成为争抢热点任务分发通常使用全局 mutex condition 保护的队列实现当所有线程同时争抢该队列时效率显然很差。更好的办法是使用更多的任务队列并调整调度算法以减少全局竞争每个系统线程拥有独立的 runqueue由一个或多个 scheduler 把用户线程分发到不同的 runqueue每个系统线程优先运行自己 runqueue 中的用户线程再考虑其他线程的 runqueue。这当然更复杂但比全局 mutex condition有更好的扩展性且这种每线程一队列的结构更容易支持 NUMA——调度器可以把任务固定到与内存亲和的核心上。核心间迁移带来 cache 惩罚当 event dispatcher 把任务递给 worker 线程时用户逻辑很可能从一个 CPU 核心跳到另一个核心并等待相应 cacheline 同步过来并不快。更优的做法是让 worker 直接运行在 event dispatcher 所在的核心上——因为尽快运行 worker的优先级通常高于从 dispatcher 获取新事件。同理收到 response 后最好在当前核心唤醒正在同步等待 RPC 的线程以避免跨核唤醒的 cache 同步成本。难题二异步编程的复杂度异步编程中的流程控制对专家也充满陷阱。任何挂起操作如 sleep 一会儿、等待某事完成都意味着用户需要显式保存状态、并在回调中恢复状态异步代码往往被迫写成状态机的形式。挂起嵌套则复杂度爆炸当挂起较少时状态机还算可把握一旦挂起发生在条件判断、循环、子函数内部写出一个能被多人理解和维护的状态机几乎不可能——而一个节点与多个节点同时交互在分布式系统中又是常态。多事件唤醒易产生竞争如果唤醒可由多种事件触发例如fd 有数据或超时了两者任一挂起和恢复过程容易出现 race condition对多线程编程能力要求很高。语法糖不能降低难度lambda 等语法糖只是让编码不那么麻烦并没有降低异步编程的固有难度。内存所有权在异步代码中难以追踪共享指针shared_ptr在异步编程中很普遍看似方便却使内存的 ownership 变得难以捉摸——内存泄漏时很难定位哪里没有释放segment fault 时也不知道哪里多释放了一次。大量使用引用计数的用户代码很难控制质量容易长期在内存问题上耗费时间若引用计数还需手动维护保持质量就更难维护者也不愿意改进。此外没有上下文栈使得 RAII 无法充分发挥作用有时需要在 callback 之外 lock、callback 之内 unlock实践中极易出错。brpc 的解答bthread一个面向网络服务的 M:N 线程库理解了上述五大模型与两大难题brpc 的设计选择就顺理成章了bthread 正是文档中面向网络服务的 M:N 线程库的工程实现。它源于分布式进程DP中的 fiber一个 N:1 协作式线程库但升级为 M:N 架构。核心目标是在保持同步编码风格的同时提升并发度、扩展性与 cache locality。其完整设计见 bthread.md。bthread 的 M:N含义是M 个 bthread 映射到 N 个 pthread 上而由于 Linux pthreadNPTL是 1:1 的这些 bthread 最终也映射到 N 个 LWP轻量级进程上。关键点是M 通常远大于 N——大量轻量的 bthread 被复用少数几个 pthread worker。设计目标与明确拒绝的方案bthread 的设计目标见 bthread.md用户保持同步编程风格几百纳秒内即可创建一个 bthread并提供多种同步原语每个 bthread API 都能从 pthread 中调用且行为合理充分利用多核更好的 cache localityNUMA 支持是加分项。同时bthread 明确拒绝了三条看似诱人的路线这正是它与通用 M:N 库的差别提供 pthread 兼容 ABI、仅靠链接替换——被拒bthread 没有优先级、并非适合所有负载静默替换会让用户不知情地使用 bthread 而引入 bug拦截所有可能阻塞的 glibc 函数与 syscall使其阻塞 bthread 而非系统线程——被拒阻塞 bthread 可能切换底层系统线程导致依赖系统 TLS 的函数行为未定义与阻塞 pthread 的函数混用可能死锁且这些 hook 通常更慢需要额外 syscall 如 epoll。这种覆盖对 N:1 协作库fiber更有效因为不 hook 的话整个系统线程都会阻塞补丁内核让 pthread 在同一核心快速切换——被拒大量 pthread 会稀释每线程资源TLS 缓存如 tcmalloc效果变差独立 bthread 库没有此问题因为它仍映射到少量 pthread 上——bthread 相对 pthread 的大部分加速正来自线程资源的集中且纯用户态实现可移植性更好。调度器源码印证每线程 runqueue work-stealing回到上一节难题一的解法——每系统线程独立 runqueue、优先运行自己的任务再考虑他人——正是 bthread 调度的骨架。在源码层面可以清晰地看到这套结构TaskControlsrc/bthread/task_control.cpp是全局调度中枢它根据-bthread_concurrency创建 N 个 pthread worker见TaskControl::init_workers.resize(_concurrency)后逐条pthread_create并为每个 worker 建立 TaskGroup。TaskGroupsrc/bthread/task_group.h是每个 worker 的私有运行环境持有本地 runqueueWorkStealingQueuebthread_t _rq和一个RemoteTaskQueue _remote_rq。worker 优先从自己的_rq取任务空则通过TaskControl::steal_task从其他 worker 的 runqueue偷任务对应文档每个系统线程优先运行自己 runqueue 中的用户线程然后再考虑其他线程的 runqueue。WorkStealingQueuesrc/bthread/work_stealing_queue.h是 Chase-Lev 式无锁任务窃取队列pop本 worker 取任务从_bottom端出队可与steal其他 worker 窃取从_top端出队并行运行_top使用BAIDU_CACHELINE_ALIGNMENT对齐以避免伪共享。默认每个 runqueue 容量由 gflag-task_group_runqueue_capacity控制默认 4096。worker 的调度循环见 src/bthread/task_group.cpp当前 bthread 挂起时worker 先弹本地 runqueue空则窃取仍空则睡下等待新任务唤醒对应 bthread.md 的 FAQ。这套结构直接回应了文档全局 mutex condition 队列性能差的批评bthread 用每 worker 独立队列 无锁窃取取代了全局互斥队列并把唤醒正在同步等待 RPC 的线程尽量放在接收响应的同一核心cache locality。同步原语 butexbthread 与 pthread 互相等待/唤醒的桥梁bthread 的第二个关键技术是butex见 src/bthread/butex.h一个 futex 风格的 32 位同步原语用于 bthread 之间以及 bthread 与 pthread 之间的等待与唤醒。butex_wait在条件不满足时挂起当前 bthread 并交出 worker 线程butex_wake/butex_wake_all唤醒等待者。它本质上把阻塞用户线程翻译成了挂起 bthread 让出 worker从而保证一个 bthread 阻塞不会阻塞其他 bthread。使用层面何时该用 bthread需要强调bthread 不是给所有人随便用的。bthread.md 的 FAQ 明确指出除非需要在一次 RPC 内并发执行多段计算否则不要直接调用 bthread API把调度交给 brpc 即可。取舍依据在 bthread_or_not.md 中给了清晰的决策框架同步 vs 异步先计算qps × latencylatency 以秒计若结果与 CPU 核数同量级用同步 API否则考虑异步。例如 qps2000、latency10ms 得 20与 32 核机器同量级 → 用同步qps100、latency5s 得 500远超核数 → 用异步。并发 RPC 不要用 bthread若只需并发发起多个 RPC用异步 API 或 ParallelChannel 更划算——bthread 的创建有调度延迟且每个 bthread 在整个 RPC 期间保持阻塞、无法复用。并行计算才用 bthread当一段搜索流程的三个阶段可并行时启动两个 bthread 跑两个阶段、第三个阶段原地运行然后 join代码示例见 bthread_or_not.md其中强调把最慢的阶段留在原地执行可掩盖 bthread 微秒级的调度延迟。此外server.md 的 worker 线程数说明 提醒进程内所有 server 与 channel共享worker pthread线程总数取所有ServerOptions.num_threads与-bthread_concurrency的最大值而非求和同步 RPC 会阻塞 worker必要时调大num_threads或用 gflag-bthread_concurrency增加客户端侧 worker各配置项汇总见 flags.md。总结从连接独占线程到M:N 线程库线程模型的发展始终围绕两个目标压榨多核与降低编程复杂度。单线程 reactor 与 N:1 fiber 简单可控但只用一核多线程 reactor 用上多核却受 cache 一致性制约、且回调风格难维护全功能 M:N 库虽理想却实现困难。brpc 的回答是在面向网络服务的简化前提下无抢占、无完备优先级用 bthread 实现 M:N 调度——每 worker 独立 runqueue 加无锁窃取缓解全局竞争、butex 让同步等待不阻塞 worker、同步编码风格保住可维护性。理解这份 threading_overview.md 中沉淀的模型谱系也就理解了 brpc 高性能与易用性背后的全部权衡。【免费下载链接】brpcbrpc is an Industrial-grade RPC framework using C Language, which is often used in high performance system such as Search, Storage, Machine learning, Advertisement, Recommendation etc. brpc means better RPC.项目地址: https://gitcode.com/GitHub_Trending/brpc/brpc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Spring Boot+Vue抽签系统:可审计、可配置、高并发的业务实现 2026/9/14 9:20:12

Spring Boot+Vue抽签系统:可审计、可配置、高并发的业务实现

简介:这是一套面向Java与前端初学者的全栈抽签系统实战项目,适用于教学演示、活动抽奖开发或Spring BootVue技术栈入门学习。资源完整覆盖后端API、前端交互与数据库设计,解决随机抽取、名单管理、结果展示等典型业务场景。压缩包共69个文件&…

阅读更多 →
基于SpringBoot的体育馆使用预约平台设计与实现(SpringBoot+Vue+MySQL) 2026/9/14 9:20:12

基于SpringBoot的体育馆使用预约平台设计与实现(SpringBoot+Vue+MySQL)

基于SpringBoot的体育馆使用预约平台设计与实现(SpringBootVueMySQL) 面向综合性体育馆的场地预约平台:篮球场、足球场、羽毛球场等多类场地在线展示,用户按时段预约场地并在线支付,管理员统筹场地、公告与论坛内容。 …

阅读更多 →
AI-AGENT开发指南:从理论到实践 2026/9/14 9:20:12

AI-AGENT开发指南:从理论到实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
强化学习路径规划算法实现:从PPO训练到ROS部署的完整指南 2026/9/14 9:20:12

强化学习路径规划算法实现:从PPO训练到ROS部署的完整指南

简介:基于Q-learning强化学习的智能机器人路径规划毕设项目,完整包含C/Qt源码、可执行程序与说明文档,面向人工智能、自动化、计算机等专业学生,可作课程设计或毕业设计基础,也适合强化学习入门实践。压缩包共66个文件…

阅读更多 →
bcrypt密码哈希技术详解与实践指南 2026/9/14 9:20:12

bcrypt密码哈希技术详解与实践指南

1. bcrypt技术概览bcrypt是一种基于Blowfish加密算法的密码哈希函数,由Niels Provos和David Mazires在1999年的USENIX会议上首次提出。它的核心设计目标是抵御彩虹表攻击和暴力破解,通过引入"盐值"(salt)和可调节的计算成本参数,使…

阅读更多 →
六大类高含金量证书揭晓!你掌握了几个? 2026/9/14 9:17:11

六大类高含金量证书揭晓!你掌握了几个?

六大类高含金量证书揭晓!你掌握了几个? 嘿!各位考证的朋友们,是否有时会感觉自己如同迷途的羔羊,迷失在考证领域的这个大森林中? 别担心,今天我将为你们揭晓通往成功的秘密武器---那就是各种让人眼花缭乱的证书!这些证书就像是你的独家密码,打开你专业成长的宝箱。 …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞