我只是一個搬運工,只爲感興趣的話題. . .
Linux 開發,使用多線程還是用 IO 複用 select/epoll?
每分鐘有2K用戶訪問,服務器端處理請求選擇用多線程(每個用戶一個線程),還是用I/O複用?
5
條評論
默認排序按時間排序
26 個回答
我是來指出排名第一的回答的BUG的:
"
多線程的模式有很多的,leader-follow還有Half-Sync/Half-Async.
現在這個年代,以我見過的代碼,真沒見過幾個是一個連接上來就一個線程的了.
"
- 多線程模型適用於處理短連接,且連接的打開關閉非常頻繁的情形,但不適合處理長連接。多線程模型默認情況下,(在Linux)每個線程會開8M的棧空間,再TCP長連接的情況下,2000/分鐘的請求,幾乎可以假定有上萬甚至十幾萬的併發連接,假定有10000個連接,開這麼多個線程需要10000*8M=80G的內存空間!即使調整每個線程的棧空間,也很難滿足更多的需求。甚至攻擊者可以利用這一點發動DDoS,只要一個連接連上服務器什麼也不做,就能吃掉服務器幾M的內存,這不同於多進程模型,線程間內存無法共享,因爲所有線程處在同一個地址空間中。內存是多線程模型的軟肋。
- "
多線程的模式有很多的,leader-follow還有Half-Sync/Half-Async.
現在這個年代,以我見過的代碼,真沒見過幾個是一個連接上來就一個線程的了.
每分鐘有2K?差點看成每秒鐘。
這2K個用戶是長連接呢還是短連接呢?如果是短連接,或者連接後通信不頻繁的話,線程(池)就可以,如果一直是2k的併發,那還是異步(感謝@程ocean提醒,補充完整:用epoll配合非阻塞IO實現,epoll本身並非異步)靠譜點。再高就要異步加並行(多線程/多進程)了。
這2K個用戶是長連接呢還是短連接呢?如果是短連接,或者連接後通信不頻繁的話,線程(池)就可以,如果一直是2k的併發,那還是異步(感謝@程ocean提醒,補充完整:用epoll配合非阻塞IO實現,epoll本身並非異步)靠譜點。再高就要異步加並行(多線程/多進程)了。
我也同意event loop + thread pool的做法,
epoll + 多線程 + 多進程部署 效率真的不錯。
先用select接口(poll/epoll,kq,iocp)接受請求,這樣可以保證併發,在這個環節他只管收,不處理業務,把FD放到一個buffer(一個q裏面),然後業務處理模型對接線程池。可以使複雜業務處理上的負擔被分擔。select+線程池,這樣兼顧了併發(犧牲了一點性能),又保證了因爲邏輯代碼的簡潔性。如果選擇完全異步的方式,你就要在業務處理裏面使用完全的異步API,至少很多數據庫驅動,緩存驅動等等你需要用的到技術都沒有提供異步API,很多業務要保障流程的正確是需要同步操作的,而且業務如果全部使用異步API,各種不明確回調和閉包導致內存暴棧的危險上升(我想各位應該被nodejs折磨過吧),對開發人員思考方式和技術實力都有較高的要求。一個部門裏面有兩個瞭解epoll就算技術非常NB的核心部門了吧,假若有能正確駕馭epoll,瞭解各種觸發方式,狀態機,特別是要能正確讀寫完整的信息,而沒有造成大量的CLOSE_WAIT,是特別特別不易的。
我曾在tornado上面搭建過一個線程池。原型參見:nikoloss/iceworld · GitHub
雖然不算最完美的解決方案,但是也在工作中省去了很多煩惱。他的效率雖沒有原生tornado高,但是非常適合多人合作(儘管如此效率還是要暴webpy幾條街)。
epoll + 多線程 + 多進程部署 效率真的不錯。
先用select接口(poll/epoll,kq,iocp)接受請求,這樣可以保證併發,在這個環節他只管收,不處理業務,把FD放到一個buffer(一個q裏面),然後業務處理模型對接線程池。可以使複雜業務處理上的負擔被分擔。select+線程池,這樣兼顧了併發(犧牲了一點性能),又保證了因爲邏輯代碼的簡潔性。如果選擇完全異步的方式,你就要在業務處理裏面使用完全的異步API,至少很多數據庫驅動,緩存驅動等等你需要用的到技術都沒有提供異步API,很多業務要保障流程的正確是需要同步操作的,而且業務如果全部使用異步API,各種不明確回調和閉包導致內存暴棧的危險上升(我想各位應該被nodejs折磨過吧),對開發人員思考方式和技術實力都有較高的要求。一個部門裏面有兩個瞭解epoll就算技術非常NB的核心部門了吧,假若有能正確駕馭epoll,瞭解各種觸發方式,狀態機,特別是要能正確讀寫完整的信息,而沒有造成大量的CLOSE_WAIT,是特別特別不易的。
我曾在tornado上面搭建過一個線程池。原型參見:nikoloss/iceworld · GitHub
雖然不算最完美的解決方案,但是也在工作中省去了很多煩惱。他的效率雖沒有原生tornado高,但是非常適合多人合作(儘管如此效率還是要暴webpy幾條街)。
- 多線程模型適用於處理短連接,且連接的打開關閉非常頻繁的情形,但不適合處理長連接。多線程模型默認情況下,(在Linux)每個線程會開8M的棧空間,再TCP長連接的情況下,2000/分鐘的請求,幾乎可以假定有上萬甚至十幾萬的併發連接,假定有10000個連接,開這麼多個線程需要10000*8M=80G的內存空間!即使調整每個線程的棧空間,也很難滿足更多的需求。甚至攻擊者可以利用這一點發動DDoS,只要一個連接連上服務器什麼也不做,就能吃掉服務器幾M的內存,這不同於多進程模型,線程間內存無法共享,因爲所有線程處在同一個地址空間中。內存是多線程模型的軟肋。
- 在UNIX平臺下多進程模型擅長處理併發長連接,但卻不適用於連接頻繁產生和關閉的情形。Windows平臺忽略此項。 同樣的連接需要的內存數量並不比多線程模型少,但是得益於操作系統虛擬內存的Copy on Write機制,fork產生的進程和父進程共享了很大一部分物理內存。但是多進程模型在執行效率上太低,接受一個連接需要幾百個時鐘週期,產生一個進程 可能消耗幾萬個CPU時鐘週期,兩者的開銷不成比例。而且由於每個進程的地址空間是獨立的,如果需要進行進程間通信的話,只能使用IPC進行進程間通 信,而不能直接對內存進行訪問。在CPU能力不足的情況下同樣容易遭受DDos,攻擊者只需要連上服務器,然後立刻關閉連接,服務端則需要打開一個進程再關閉。
- 同時需要保持很多的長連接,而且連接的開關很頻繁,最高效的模型是非阻塞、異步IO模型。而且不要用select/poll,這兩個API的有着O(N)的時間複雜度。在Linux用epoll,BSD用kqueue,Windows用IOCP,或者用libevent封裝的統一接口(對於不同平臺libevent實現時採用各個平臺特有的API),這些平臺特有的API時間複雜度爲O(1)。 然而在非阻塞,異步I/O模型下的編程是非常痛苦的。由於I/O操作不再阻塞,報文的解析需要小心翼翼,並且需要親自管理維護每個鏈接的狀態。並且爲了充分利用CPU,還應結合線程池,避免在輪詢線程中處理業務邏輯。
但這種模型的效率是極高的。以知名的http服務器nginx爲例,可以輕鬆應付上千萬的空連接+少量活動鏈接,每個連接連接僅需要幾K的內核緩衝區,想要應付更多的空連接,只需簡單的增加內存(數據來源爲淘寶一位工程師的一次技術講座,並未實測)。這使得DDoS攻擊者的成本大大增加,這種模型攻擊者只能將服務器的帶寬全部佔用,才能達到目的,而兩方的投入是不成比例的。
雖然藍形參(排名第一的那位)說得不好。但是,我覺得也沒有誤人子弟那麼嚴重。至少有很多地方還是說得不錯得。這裏我贊一個。
我糾正一下藍大哥的第一點。如果只有多線程而沒有其他的機制的話,那系統確實只能一個connection對應一個線程(非常危險的做法。線程掛了,你係統就over了)。
我也非常同意:event loop + thread pool 的做法。
我糾正一下藍大哥的第一點。如果只有多線程而沒有其他的機制的話,那系統確實只能一個connection對應一個線程(非常危險的做法。線程掛了,你係統就over了)。
我也非常同意:event loop + thread pool 的做法。
有幾種網絡服務器模型分析如下
1. 一個連接一個線程模型:適用場景,連接少,且邏輯複雜。例如mysql採用此模型,一個連接一個線程。模型的一些小變體是線程採用線程池,避免創建銷燬線程的開銷
2. 半同步半異步模型:單獨一個IO線程來異步處理網絡IO,使用線程池來同步處理請求,業務邏輯的編寫就會變得簡單。適用於併發連接較多,但是每秒的請求量不太大的業務。可以參考
半同步半異步I/O的設計模式(half sync/half async)
也可以參考我的
handy/hsha.cc at master · yedf/handy · GitHub
Leader/Follower模式屬於這個模型的變體
3. 全異步模型:網絡IO和業務處理都是異步的,一個線程可以處理所有的任務,程序不會阻塞在任何一個網絡IO或者磁盤IO上。這種模型能夠最大程度利用計算機的性能,但是全異步的處理讓業務的編寫變得非常複雜。nginx這是這種模型,他是多進程,每個worker都是一樣的,沒有不同。memcache也是類似的,它的多線程完全是爲了利用多個cpu能力,單線程也能夠完整的跑整個業務的。
樓主的每分鐘2k用戶,按照通常的訪問情況,每秒也就4,5個請求,最適合應該是半同步半異步模型。
1. 一個連接一個線程模型:適用場景,連接少,且邏輯複雜。例如mysql採用此模型,一個連接一個線程。模型的一些小變體是線程採用線程池,避免創建銷燬線程的開銷
2. 半同步半異步模型:單獨一個IO線程來異步處理網絡IO,使用線程池來同步處理請求,業務邏輯的編寫就會變得簡單。適用於併發連接較多,但是每秒的請求量不太大的業務。可以參考
半同步半異步I/O的設計模式(half sync/half async)
也可以參考我的
handy/hsha.cc at master · yedf/handy · GitHub
Leader/Follower模式屬於這個模型的變體
3. 全異步模型:網絡IO和業務處理都是異步的,一個線程可以處理所有的任務,程序不會阻塞在任何一個網絡IO或者磁盤IO上。這種模型能夠最大程度利用計算機的性能,但是全異步的處理讓業務的編寫變得非常複雜。nginx這是這種模型,他是多進程,每個worker都是一樣的,沒有不同。memcache也是類似的,它的多線程完全是爲了利用多個cpu能力,單線程也能夠完整的跑整個業務的。
樓主的每分鐘2k用戶,按照通常的訪問情況,每秒也就4,5個請求,最適合應該是半同步半異步模型。
楓亦 程序員
這個問題我認爲還是得看需求吧,如果要尋求通用簡單的方式用語義更加豐富的語言比如go或許更合適一些。對於短連接和長連接,有些場合是這樣的:剛剛接入的前幾次通信可能是短連接,之後可能轉入長連接、或者斷開、或者一直維持短連接。對於這樣的需求,那麼對剛剛接入的連接我認爲沒必要單獨拉一個工作線程,我喜歡把這些連接放在一個公共的工作線程中處理,維護一組連接狀態機。如果達到觸發長連接的條件,則單獨拉一個工作線程進行處理。IO機制方面,公共的工作線程中需要同時監聽多個套接字,我一般採用select/poll或者epoll,而轉入長連接的工作線程我直接用阻塞同步方式,當然肯定需要弄個阻塞時間上限。目前看我的這種解決方式對於我們的小需求表現還能接受,合適就好啦。
匿名用戶
這個問題真的是老生常談了,初期可以把幾個方案都做做,能學到不少東西。但是一旦你熟練掌握了,做得太多跟月經似的週期性時,我非常認真的建議你用 Erlang,實在不行用 Go 也不錯,再不行如果處理的都是些 Web 方面的小業務而且對容錯性的要求不是很高,那用 node.js 也比整天挖 C/C++ 裏的 epool/kqueue/IOCP 那點老墳有趣多了。
網絡 IO 模型就那麼幾種,儘管套着所謂高性能的大帽子,但不管哪種其實用起來都很蹩腳。因爲根源在於 C/C++ 對併發的支持實在是可憐到髮指。只有庫級別的支持,沒有任何語義層面的機制。
C/C++ 在服務端高併發IO密集型業務方面生產效率和可維護性方面低得令人髮指,完全抵消掉了那一點點性能方面的增益。
最後再強調一遍,學習一下各種網絡模型有百利無一害,但也僅此而已,在生產環境中你應該用一些更可靠的實現,這方面的輪子真的很無聊。
網絡 IO 模型就那麼幾種,儘管套着所謂高性能的大帽子,但不管哪種其實用起來都很蹩腳。因爲根源在於 C/C++ 對併發的支持實在是可憐到髮指。只有庫級別的支持,沒有任何語義層面的機制。
C/C++ 在服務端高併發IO密集型業務方面生產效率和可維護性方面低得令人髮指,完全抵消掉了那一點點性能方面的增益。
最後再強調一遍,學習一下各種網絡模型有百利無一害,但也僅此而已,在生產環境中你應該用一些更可靠的實現,這方面的輪子真的很無聊。
知乎用戶 libconcurrency,http://github.com/zhouzhenghui
很多人關注這個問題,我相信焦點不在於每分鐘多少k用戶上,而是這兩種方式帶來在編程結構上的差異。
不談多線程在性能上的優劣勢,在單核時代就已經被普遍應用的這個技術主要帶來的變化是邏輯隔離。代碼的複雜性相對被簡化了,也減少了局部不斷重構的需求。
如今多核時代下,我們還需要多線程壓榨出足夠的性能,同時避免過多線程帶來的調度開銷。結合上述編碼的需求,需要在某些方面作出根本的變革。
Erlang和go走在正確的路線上,但還是不夠或存有技術上的瑕疵。
不談多線程在性能上的優劣勢,在單核時代就已經被普遍應用的這個技術主要帶來的變化是邏輯隔離。代碼的複雜性相對被簡化了,也減少了局部不斷重構的需求。
如今多核時代下,我們還需要多線程壓榨出足夠的性能,同時避免過多線程帶來的調度開銷。結合上述編碼的需求,需要在某些方面作出根本的變革。
Erlang和go走在正確的路線上,但還是不夠或存有技術上的瑕疵。