muduo学习笔记:net部分之多线程TCP服务端的设计模式演进

本文介绍了网络编程的阻塞式和非阻塞式方案,包括accept+read/write、accept+fork、accept+thread、preforked等。重点探讨了单线程Reactor、Reactor+线程池、多线程Reactor等非阻塞方案,分析了它们在并发处理、CPU利用率和资源管理方面的优缺点,适用于不同场景的选择。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


【muduo学习笔记:net部分之EventLoopThread和EventThreadPool】介绍事件循环的线程池,这里以muduo中Sudoku Solver 为例设计的服务端设计模式。Sudoku 是一个计算密集型的任务,可以同时服务多个客户连接。因此要求服务端能够利用多核硬件能力,同时不能阻塞服务端。

1、阻塞式的方案

阻塞式方案在网络库很少使用,仅简单说明。

(1)accept + read/write

不是一个并发服务器,而是 iterative 服务器,因为它一次只能服务一个客户。不适合长连接,到是很适合 daytime 这种 write-only 服务。

(2)accept + fork

统的 Unix 并发网络编程方案,适合并发连接数不大的情况,适合“计算响应的工作量远大于 fork() 的开销”这种情况,比如数据库服务器。这种方案适合长连接,但不太适合短连接,因为 fork() 开销大于求解 sudoku 的用时。

(3)accept + thread

在 Java 1.4 引入 NIO 之前,传统的 Java 网络编程方案 thread-per-connection,开销比方案 accept + fork 要小很多。这种方案的伸缩性受到线程数的限制,一两百个还行,几千个的话对操作系统的 scheduler 恐怕是个不小的负担。

(4)preforked

accept + fork 方案的优化,考虑了惊群的问题。参考UNP[2-5].

(5)preforked

accept + thread 方案的优化,预先分配一些线程,降低线程销毁创建的性能损耗。也就是线程池的好处。

2、非阻塞式的方案

非阻塞方式是主流,根据资源情况、也无需求灵活决定使用哪一种方式。

2.1、单线程Reactor

基本的单线程 reactor 方案,作为后续对比的基本点。这种方案的优点是由网络库搞定数据收发,程序只关心业务逻辑;缺点在前面已经谈了:适合 IO 密集的应用,不太适合 CPU 密集的应用,因为较难发挥多核的威力。
在这里插入图片描述

2.2、单线程Reactor + thread

一个过渡方案,收到 Sudoku 请求之后,不在 reactor 线程计算,而是创建一个新线程去计算,以充分利用多核 CPU。这是非常初级的多线程应用,因为它为每个请求(而不是每个连接)创建了一个新线程。这个开销可以用线程池来避免,即方案2.4。

有一个缺点,out-of-order,即同时创建多个线程去计算同一个连接上收到的多个请求,那么算出结果的次序是不确定的。因此,在开始始设计协议的时候使用id,以便客户端区分 response 对应的是哪个 request。

2.3、单线程Reactor + prethread

为了让返回结果的顺序确定,我们可以为每个连接创建一个计算线程,每个连接上的请求固定发给同一个线程去算,先到先得。这也是一个过渡方案,因为并发连接数受限于线程数目。

与2.2方案方案对比,区别是一个 client 的最大 CPU 占用率。在方案 2.2 中,一个 connection 上发来的一长串突发请求(burst requests) 可以占满全部 8 个 core;而在方案 2.3中,由于每个连接上的请求固定由同一个线程处理,那么它最多占用 12.5% 的 CPU 资源。这两种方案各有优劣,取决于应用场景的需要,到底是公平性重要还是突发性能重要。这个问题在方案2.4和2.5同样存在。

2.4、单线程Reactor + work thread pool

为了弥补方案 2.3 中为每个请求创建线程的缺陷,我们使用固定大小线程池,程序结构如下图。全部的 IO 工作都在一个 reactor 线程完成,而计算任务交给 thread pool。如果计算任务彼此独立,而且 IO 的压力不大,那么这种方案是非常适用的。Sudoku Solver 正好符合。
在这里插入图片描述
如果 IO 的压力比较大,一个 reactor 忙不过来,可以试试 multiple reactors 的方案 2.5.

2.5、Multiple Reactors(moduo默认线程模型)

main Reactor(EventLoop) + sub Reactors( EventThreadLoopThread )。

muduo内置使用多线程方案,模仿Netty的模式特点one loop per thread,一个main reactor 负责 accept 连接,然后把连接挂载sub reactor上(使用 round-robin 的方式来选择 sub reactor),该连接的的所有IO事件全部在其sub reactor所处的的线程中处理。

线程池数量通常根据cpu个数设定,充分利用CPU,总体处理能力不会随连接数增加而下降。由于一个连接完全由一个线程管理,请求的顺序性有保证,突发请求也不会占满全部 8 个核(如果需要优化突发请求,可以考虑方案 2.6)

这种方案把 IO 分派给多个线程,防止出现一个 reactor 的处理能力饱和。与方案 2.4的线程池相比,当前方案减少了进出 thread pool 的两次上下文切换。认为这是一个适应性很强的多线程 IO 模型,把它作为 muduo 的默认线程模型。
在这里插入图片描述

2.6、Multiple Reactors + work thread pool

方案2.4和方案2.5的结合。既使用多个 reactors 来处理 IO,又使用线程池来处理计算。这种方案适合既有突发 IO (利用多线程处理多个连接上的 IO),又有突发计算的应用(利用线程池把一个连接上的计算任务分配给多个线程去做)。

简单说,一个main reactor负责处理新的连接,多个sub reactors负责所有连接的IO事件。 IO事件中CPU密集计算的耗时任务,放到ThreadPoll中处理。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

aworkholic

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值