《Linux多线程服务端编程》笔记——多线程服务器的适用场合
2016-08-23 16:30 shuaihanhungry 阅读(2906) 评论(0) 编辑 收藏 举报如果要在一台多核机器上提供一种服务或执行一个任务,可用的模式有
- 运行一个单线程的进程
- 运行一个多线程的进程
- 运行多个单线程的进程
- 运行多个多线程的进程
这些模式之间的比较已经是老生常谈,简单地总结
- 模式 1 是不可伸缩的 (scalable),不能发挥多核机器的计算能力;
- 模式 3 是目前公认的主流模式。它有两种子模式:
3a 简单地把模式 1 中的进程运行多份,如果能用多个 tcp port 对外提供服务的话;
3b 主进程+woker进程,如果必须绑定到一个 tcp port,比如 httpd+fastcgi。 - 模式 2 是很多人鄙视的,认为多线程程序难写,而且不比模式 3 有什么优势;
- 模式 4 更是千夫所指,它不但没有结合 2 和 3 的优点,反而汇聚了二者的缺点。
据我所知,有两种场合必须使用单线程
- 程序可能会 fork()。
- 限制程序的 CPU 占用率。
单线程程序的优缺点
- event loop 有一个明显的缺点,它是非抢占的(non-preemptive)。假设事件 a 的优先级高于事件 b,处理事件 a 需要 1ms,处理事件 b 需要 10ms。如果事件 b 稍早于 a 发生,那么当事件 a 到来时,程序已经离开了 poll() 调用开始处理事件 b。事件 a 要等上 10ms 才有机会被处理,总的响应时间为 11ms。这等于发生了优先级反转。这可缺点可以用多线程来克服,这也是多线程的主要优势。
- 如果用很少的 CPU 负载就能让的 IO 跑满,或者用很少的 IO 流量就能让 CPU 跑满,那么多线程没啥用处。
多线程的适用场景
- 提高响应速度,让 IO 和“计算”相互重叠,降低 latency。虽然多线程不能提高绝对性能,但能提高平均响应性能。
模式 2 和模式 3a 该如何取舍
如果工作集较大,那么就用多线程,避免 CPU cache 换入换出,影响性能;否则,就用单线程多进程,享受单线程编程的便利。
一个程序要做成多线程的,大致要满足
- 有多个 CPU 可用。单核机器上多线程的优势不明显;
- 线程间有共享数据。如果没有共享数据,用模型 3b 就行。虽然我们应该把线程间的共享数据降到最低,但不代表没有;
- 共享的数据是可以修改的,而不是静态的常量表。如果数据不能修改,那么可以在进程间用 shared memory,模式 3 就能胜任;
- 提供非均质的服务。即,事件的响应有优先级差异,我们可以用专门的线程来处理优先级高的事件。防止优先级反转;
- latency 和 throughput 同样重要,不是逻辑简单的 IO bound 或 CPU bound 程序;
- 利用异步操作。比如 logging。无论往磁盘写 log file,还是往 log server 发送消息都不应该阻塞 critical path;
- 能 scale up。一个好的多线程程序应该能享受增加 CPU 数目带来的好处,目前主流是 8 核,很快就会用到 16 核的机器了;
- 具有可预测的性能。随着负载增加,性能缓慢下降,超过某个临界点之后急速下降。线程数目一般不随负载变化;
- 多线程能有效地划分责任与功能,让每个线程的逻辑比较简单,任务单一,便于编码。而不是把所有逻辑都塞到一个 event loop 里,就像 Win32 SDK 程序那样。
一个多线程服务程序中的线程大致可分为 3 类
- IO 线程,这类线程的的主循环是 io multiplexing,等在 select/poll/epoll 系统调用上。这类线程也处理定时事件。当然它的功能不止 IO,有些计算也可以放入其中;
- 计算线程,这类线程的主循环是 blocking queue,等在 condition variable 上。这类线程一般位于 thread pool 中;
- 第三方库所用的线程,比如 logging,又比如 database connection。
参考:《Linux多线程服务端编程》。