本章研究對象:分佈式計算的網絡應用程序,基本功能可以被簡單歸納為“收到數據,算一算,發出去”
單線程服務器
最常用的為“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,線程池太小,會不能高效利用硬件,太大會導致額外的線程切換和內存開銷
多線程不能減少工作量,而是一種統籌的思路讓工作提早結束。