博客 / 詳情

返回

多線程服務器編程[3]-多線程服務器的使用場合和常用模型

本章研究對象:分佈式計算的網絡應用程序,基本功能可以被簡單歸納為“收到數據,算一算,發出去”

單線程服務器

最常用的為“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,線程池太小,會不能高效利用硬件,太大會導致額外的線程切換和內存開銷

多線程不能減少工作量,而是一種統籌的思路讓工作提早結束。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.