常見多執行緒與併發伺服器設計方案舉例
一、3點基礎知識
1、一個主機的埠號為所有程式所共享,但普通使用者程式繫結bind不了一些特殊埠號如20、80等。
多個程式不能同時監聽listen同一個埠,會失敗。當然父程式可以先listen然後fork多個子程式,多個子程式都可以accept這個sock,即搶奪式響應(驚群效應)。
關注4元組是否能唯一確定一個連線?
2、每個程式都有自己的檔案描述符(包括file fd, socket fd, timer fd, event fd, signal fd),一般是1024,可以通過ulimit -n 設定,但所有程式開啟的檔案描述符總數有上限,跟主機的記憶體有關。
3、一個程式內的所有執行緒共享程式的檔案描述符。
二、常見併發伺服器方案:
無法充分利用多核CPU,不適合執行時間較長的服務,即適用於短連線。如果是長連線則需要在read/write之間迴圈,那麼只能服務一個客戶端。
![](https://i.iter01.com/images/2b3f9915939d1053844e9974f701ecb389af64bbcb2aecd1215296912a418bb4.png)
2、併發式(concurrent)伺服器
one connection per process/one connection per thread
one connection per process/one connection per thread
適合執行時間比較長的服務
![](https://i.iter01.com/images/963d8fb7c116ab0550299dd3c7c711df4585cb2c1d7d1026a6ea094efd7c0df4.png)
![](https://i.iter01.com/images/a6670fdab6e326ee836adac6223b9f08349b94221d0bbd5bca7f1d37a0dc5e9e.png)
one connection per process : 主程式每次fork 之後要關閉connfd,子程式要關閉listenfd
one connection per thread : 主執行緒每次accept 回來就建立一個子執行緒服務,由於執行緒共享檔案描述符,故不用關閉。
3、prefork or pre threaded(UNP2e 第27章)(容易發生“驚群”現象,即多個子程式都處於accept狀態)
![](https://i.iter01.com/images/35b42c53ce087adf52eb5010eef5e35c59a188d0621f0f056db02d7f0d3412db.png)
4、反應式( reactive )伺服器 (reactor模式)(select/poll/epoll)
併發處理多個請求,實際上是在一個執行緒中完成。無法充分利用多核CPU
不適合執行時間比較長的服務,所以為了讓客戶感覺是在“併發”處理而不是“迴圈”處理,每個請求必須在相對較短時間內執行。
併發處理多個請求,實際上是在一個執行緒中完成。無法充分利用多核CPU
不適合執行時間比較長的服務,所以為了讓客戶感覺是在“併發”處理而不是“迴圈”處理,每個請求必須在相對較短時間內執行。
![](https://i.iter01.com/images/cca662f01d12b45f63c66b4b381e3be4f4c4b22b9fcc30b7a387c56433842d68.png)
5、reactor + thread per request(過渡方案)
6、reactor + worker thread(過渡方案)
7、reactor + thread pool(能適應密集計算)
![](https://i.iter01.com/images/3e9039c8f2e9e455fbfc7d3fa2d6b384603c35e6402446c1f831529458189ab4.png)
muduo庫中的/example/suduku/ 中有這樣一個例子,因為數獨求解是計算密集型任務。
在實踐中為了reactor能快速回到事件迴圈去響應請求,經常將讀到的資料put到一個環形記憶體佇列(一般記憶體or共享記憶體),而thread pool的執行緒則從中讀取進行資料計算。
8、multiple reactors(能適應更大的突發I/O)
reactors in threads(one loop per thread)
reactors in processes
reactors in processes
一般來說一個subReactor適用於一個千兆網口
![](https://i.iter01.com/images/d9b9a2c0e4a4c74d80e527e694aaecd26e2bdcf859cc6a9d087cb559b545675b.png)
9、multiple reactors + thread pool(one loop per thread + threadpool)(突發I/O與密集計算)
subReactor可以有多個,但threadpool只有一個。
![](https://i.iter01.com/images/452bf6bf915bdc3a35ce36c71404519a5ec4ed763b45e19dc4f83718e8cfb06c.png)
10、proactor伺服器(proactor模式,基於非同步I/O)
理論上proactor比reactor效率要高一些非同步I/O能夠讓I/O操作與計算重疊。充分利用DMA特性。Linux非同步IO
glibc aio(aio_*),有bugkernel native aio(io_*),也不完美。目前僅支援 O_DIRECT 方式來對磁碟讀寫,跳過系統快取。要自已實現快取,難度不小。
boost asio實現的proactor,實際上不是真正意義上的非同步I/O,底層是用epoll來實現的,模擬非同步I/O的。
![](https://i.iter01.com/images/aec5390b672adb4e74aa6bfcd797075a4706cd06d702dfcd4290363e71f791f5.png)
常見併發伺服器方案比較:
![](https://i.iter01.com/images/4d9a0c03ec5a91be5a4eb10fb7f5f1b9ba56ddd2425b42beba0e3975456a3c86.png)
三、一些常見問題
1、Linux能同時啟動多少個執行緒?
對於 32-bit Linux,一個程式的地址空間是 4G,其中使用者態能訪問 3G 左右,而一個執行緒的預設棧 (stack) 大小是 8M,心算可知,一個程式大約最多能同時啟動 350 個執行緒左右。
2、多執行緒能提高併發度嗎?
如果指的是“併發連線數”,不能。
假如單純採用 thread per connection 的模型,那麼併發連線數大約350,這遠遠低於基於事件的單執行緒程式所能輕鬆達到的併發連線數(幾千上萬,甚至幾萬)。所謂“基於事件”,指的是用 IO multiplexing event loop 的程式設計模型,又稱 Reactor 模式。
3、多執行緒能提高吞吐量嗎?
對於計算密集型服務,不能。
如果要在一個8核的機器上壓縮100個1G的文字檔案,每個core的處理能力為200MB/s,那麼“每次起8個程式,一個程式壓縮一個檔案”與“只啟動一個程式(8個執行緒併發壓縮一個檔案)”,這兩種方式總耗時相當,但是第二種方式能較快的拿到第一個壓縮完的檔案。
4、多執行緒能提高響應時間嗎?
可以。參考問題3
5、多執行緒程式日誌庫要求
執行緒安全,即多個執行緒可以併發寫日誌,兩個執行緒的日誌訊息不會出現交織。
用一個全域性的mutex保護IO每個執行緒單獨寫一個日誌檔案
前者造成全部執行緒搶佔一個鎖(序列寫入)
後者有可能讓業務執行緒阻塞在寫磁碟操作上。(磁碟IO時間比較長)
解決辦法:用一個logging執行緒負責收集日誌訊息,並寫入日誌檔案,其他業務執行緒只管往這個“日誌執行緒”傳送日誌訊息(如通過BlockingQueue提供介面),這稱為“非同步日誌”,也是一個經典的生產者消費者模型。
後者有可能讓業務執行緒阻塞在寫磁碟操作上。(磁碟IO時間比較長)
解決辦法:用一個logging執行緒負責收集日誌訊息,並寫入日誌檔案,其他業務執行緒只管往這個“日誌執行緒”傳送日誌訊息(如通過BlockingQueue提供介面),這稱為“非同步日誌”,也是一個經典的生產者消費者模型。
6、線程池大小的選擇
如果池中執行任務時,密集計算所佔時間比重為P(0<P<=1),而系統一共有C個CPU,為了讓C個CPU跑滿而不過載,執行緒池大小的經驗公式T=C/P,即T*P=C(讓CPU剛好跑滿 )
假設C=8,P=1.0,執行緒池的任務完全密集計算,只要8個活動執行緒就能讓CPU飽和假設C=8,P=0.5,執行緒池的任務有一半是計算,一半是IO,那麼T=16,也就是16個“50%繁忙的執行緒”能讓8個CPU忙個不停。
7、執行緒分類
I/O執行緒(這裡特指網路I/O)
計算執行緒
第三方庫所用執行緒,如logging,又比如database
計算執行緒
第三方庫所用執行緒,如logging,又比如database
參考:
《UNP》
muduo manual.pdf
《linux 多執行緒伺服器程式設計:使用muduo c++網路庫》
http://www.ibm.com/developerworks/cn/linux/l-async/https://domsch.com/linux/lpc2010/Scaling_techniques_for_servers_with_high_connection%20rates.pdf
相關文章
- 【多執行緒與高併發3】常用鎖例項執行緒
- 多執行緒併發程式設計“鎖”事執行緒程式設計
- 併發程式設計與執行緒安全程式設計執行緒
- 多執行緒與高併發(二)執行緒安全執行緒
- 併發與多執行緒之執行緒安全篇執行緒
- 多執行緒與高併發(一)多執行緒入門執行緒
- 多執行緒與併發----Semaphere同步執行緒
- 併發與多執行緒基礎執行緒
- java多執行緒與併發 - 執行緒池詳解Java執行緒
- java多執行緒與併發 - 併發工具類Java執行緒
- 【多執行緒與高併發】- 執行緒基礎與狀態執行緒
- 併發程式設計之多執行緒執行緒安全程式設計執行緒
- 【多執行緒高併發程式設計】二 實現多執行緒的幾種方式執行緒程式設計
- 多執行緒與併發----讀寫鎖執行緒
- Java多執行緒與併發之ThreadLocalJava執行緒thread
- JAVA多執行緒併發Java執行緒
- 多執行緒併發:以AQS中acquire()方法為例來分析多執行緒間的同步與協作執行緒AQSUI
- 併發程式設計之:執行緒程式設計執行緒
- 併發程式設計之volatile與JMM多執行緒記憶體模型程式設計執行緒記憶體模型
- 多執行緒併發篇——如何停止執行緒執行緒
- C++11併發程式設計:多執行緒std::threadC++程式設計執行緒thread
- Java執行緒與併發程式設計實踐----額外的執行緒能力Java執行緒程式設計
- 多執行緒與併發-----Lock鎖技術執行緒
- 【多執行緒與高併發】- 淺談volatile執行緒
- Java高併發與多執行緒(一)-----概念Java執行緒
- 【多執行緒與高併發 2】volatile 篇執行緒
- Java高併發與多執行緒(二)-----執行緒的實現方式Java執行緒
- Java 多執行緒與併發程式設計 · Java 工程師必知必會Java執行緒程式設計工程師
- Java併發指南1:併發基礎與Java多執行緒Java執行緒
- 從執行緒到併發程式設計執行緒程式設計
- Java併發程式設計:Java執行緒Java程式設計執行緒
- java併發程式設計——執行緒池Java程式設計執行緒
- java併發程式設計——執行緒同步Java程式設計執行緒
- 多執行緒併發同步問題及解決方案執行緒
- 多執行緒併發鎖分類以及簡單例項執行緒單例
- 多執行緒高併發程式設計(10) -- ConcurrentHashMap原始碼分析執行緒程式設計HashMap原始碼
- 程式設計體系結構(05):Java多執行緒併發程式設計Java執行緒
- Java併發程式設計之執行緒安全、執行緒通訊Java程式設計執行緒
- 多執行緒與併發----CycliBarrier、CountDownLatch 和 Exchanger同步執行緒CountDownLatch