本章钻研对象:分布式计算的网络应用程序,基本功能能够被简略演绎为“收到数据,算一算,收回去”
单线程服务器
最罕用的为“non-blocking IO + IO multiplexing”, 即 Reactor 模式,例如
- lighttpd
- Nginx
- libevent
- Java NIO
- Twisted(Python)
此外还有 ASIO 应用的 Proactor 模式
Reactor
构造
- 事件循环(Event-loop)
- 应用 epoll 进入阻塞监听事件,依照事件类型调用 hanlder
特点
-
须要非阻塞编程,回调函数必须是非阻塞的,只能在 epoll 处阻塞
- 这导致容易割裂业务逻辑,不容易了解与保护
-
实用于 IO 密集型
- 计算太多也会阻塞
- 尽管单线程模型不须要思考同步问题,但不能很好的利用多核,而如果同时应用多个过程,则势必会波及到同步问题
多线程服务器
- 阻塞 + 每个申请新建一个线程
- 阻塞 + 线程池
- 非阻塞 + IO multiplexing 也即作者所称的“non-blocking IO + one loop per thread”
- Leader/Follower 等高级模式
One loop per thread
益处
- 线程数目固定
- 不便地在线程间调配负载
- IO 事件产生的线程是固定的,一个连贯会被注册到某个线程的 loop 中,并始终由这个线程负责,同一个 TCP 连贯不必思考并发
但多线程的 loop 相比单线程的 loop 的要求变高,即须要做到线程平安,重要的问题是“如何容许一个线程往另一个线程的 loop 里塞货色?”
线程池
- 实用计算工作较多的状况,重要的基础设施是 BlockingQueue 实现的工作队列或生产者消费者队列
- 把计算工作或待计算数据调配给线程池中的线程
过程间通信只用 TCP
其余 IPC 办法: pipe,FIFO,音讯队列,共享内存,信号
TCP 的益处:跨主机,伸缩性,双向,无负作用,可记录,可重现,容易定位故障
多线程服务器的实用场合
解决并发连贯的两种形式
-
Go goroutine,python gevent 等的语言提供的 用户态线程,由语言的 runtime 自行调度
- 便宜,能够大量创立
- 通常配合“看起来是阻塞的 IO”,这里指对于用户态的调度器阻塞,而不是对于操作系统阻塞,否则用户态多线程无奈成立
-
内核级线程 配合 Reactor 模式,例如 libevent,muduo,Netty
- 数量与 CPU 数目相当
- 非阻塞 IO
因为 C ++ 并没有第一类的工具,所以本书只思考第二类
model 剖析
- 单过程 + 单线程:没有伸缩性
- 单过程 + 多线程
-
多过程 + 单线程
- 复制多份单线程的过程,应用多个 TCP port 提供服务
- 主过程 +worker 过程
- 多过程 + 多线程:汇集了 2 和 3 的毛病
单线程
- 须要 fork 的时候
- 须要限度程序 CPU 占用率的时候
- 单线程的 Reactor 的次要毛病是响应工夫慢,多线程改善这一点
多线程
- IO 吞吐(网络 / 磁盘)是瓶颈时,多线程不会减少吞吐,这时候单线程 + 单过程就够了
- CPU 算力是瓶颈时,多线程不会减少吞吐,这时候单线程 + 多过程更适合也更简略
- 比照多过程
8 外围,压缩 100 个文件
多过程:每个过程压缩 1 个文件
多线程:每个文件用 8 个线程并行压缩
总耗时雷同,因为 CPU 都是满载的,然而后者能更快拿到第一个压缩完的文件,这体现了多线程的低提早
但实际上,线程间算法的并行度很难达到 100%,所以:同一个工作,多线程 或者 会比多过程吞吐量降落一点,但 肯定 能够带来响应工夫的晋升。
多线程的益处在于
- 能 进步响应速度,让 IO 和计算重叠
- 相比过程每次切换都使 CPU Cache 生效,线程间切换老本小,因而实用于“工作集”比拟大的状况
- 宰割事务,将 IO 线程、计算线程和第三方库(例如 logging)线程离开
多线程应用 BlockingQueue 在过程间传递数据
- 计算操作:一个 IO 线程,8 个计算线程(线程池),IO 线程向 BlockingQueue 中增加数据,计算线程收到唤醒后开始计算
-
写操作:一个独自的 logging 线程,通过一个或多个 BlockingQueue 对外提供接口,别的线程把日志写入 Queue 中即可,不须要期待,这样升高了服务线程的响应工夫
- 留神,读操作并不能利用多线程的便利性,因为无论如何都须要等到后果之后能力持续进行
线程池
- 当计算在每次响应申请中占比比拟高时(20% 以上)适宜
- 能简化编程;防止实时创立线程的开销;加重 IO 线程的累赘(IO 线程进行计算的话,就不能相应 IO 了,会导致响应速度变差)
- 线程池的大小须要阻抗匹配,例如 8 核 CPU,每个计算工作线程的密集计算所占工夫比重为 50%,那么 16 个线程就能跑满全副 CPU,线程池太小,会不能高效利用硬件,太大会导致额定的线程切换和内存开销
多线程不能缩小工作量,而是一种兼顾的思路让工作提前完结。