本章研究对象:分布式计算的网络应用程序,基本功能可以被简单归纳为“收到数据,算一算,发出去”
单线程服务器
最常用的为“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,线程池太小,会不能高效利用硬件,太大会导致额外的线程切换和内存开销
多线程不能减少工作量,而是一种统筹的思路让工作提早结束。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。