高並發和高性能系統中進程、線程、協程、隊列的詳解,以及各運行模式的對比
來自專欄猿論28 人贊了文章
【回顧上集內容】
之前的文章《想要做到高並發和高性能,請先真正的理解它們,以及跟CPU,內存,分散式又有什麼關係呢》有講到進程、線程、協程、隊列對內存需求的差別,然後在系統中各自可以維持的並發數對比數據。
也一筆帶過對應的幾種運行模式。
我們先來做一個假設,單個進程內存佔用10M,單個線程內存佔用2M,單個協程內存佔用20K,隊列任務內存佔用2K,我們下面來看看內存與並發量的關係。
(具體的內存佔用大小在不同的應用場景中會有很大的不同,所以這裡只是為了方便計算而做的一個假設)內存量 進程數 線程數 協程 隊列任務
1G 100 500 50K 500K 2G 200 1000 100K 1000K 4G 400 2000 200K 2000K 8G 800 4000 400K 4000K 對應的幾種運行模式 多進程: php fast-cgi 多線程: java web 協程: go 隊列: nginx
這一次,我們再來詳細的講解進程、線程、協程、隊列,以及這幾種運行模式的特點和優勢劣勢對比。
【進程介紹】
每一個應用運行起來都會有自己的進程,因為進程是系統資源分配的基本單位。
在線程出現之前,進程也是CPU調度的基本單位。
每一個進程創建出來,都會分配三種基本的內存資源,分別是代碼段、數據段和堆棧段。
代碼段和數據段分別保存著應用的執行代碼和全局變數、常量、靜態變數,這些就是不會變化或者很少變化的內容,當然內存佔用相對也會比較少。
而應用運行起來,需要的更多資源就會在堆棧中用到。
其中堆空間是存放各種變數數據的地方,內存大小也是可以動態調整的。
而棧空間是子任務(線程、協程)獨立存放自己的數據地方,比如:函數調用、參數、返回值和局部變數。
這樣一來,子任務(線程、協程)之間就可以獨立運行,而且還可以共享堆空間中的變數數據。
【線程介紹】
線程在新的操作系統中,也稱為輕量級進程,因為現在的線程已經是CPU調度的基本單位了。
操作系統不僅僅維持一個進程表,而且還會維持一個線程表,這樣操作系統就可以把線程作為調度單位。
線程是進程內創建,可以共享進程的資源,所以,線程自身獨立的資源依賴就會少很多,因為只需要為每個線程分配獨立的棧空間。
而線程的棧空間是固定大小的,如果程序比較複雜,或者裡面的數據量大,為了不出現「棧空間不足」的錯誤,就必須把棧空間設置的足夠大才行。
於是,線程是固定的棧空間S(足夠大),總共運行多少線程T,佔用總的棧空間就可以簡單計算出來=T*S。
這個資源佔用量相對T個進程來說,還是少了很多的,畢竟線程是共享了進程的代碼段、數據段和堆空間。
【協程介紹】
協程是可以在應用態協作的程序,它的調度不是操作系統處理,而是應用系統自己來調度處理,也稱為輕量級線程。
在操作系統可以獨立調度線程之前,在線程還是作為應用的程序包,有應用程序自己調度和管理的時候,其實那種線程也就跟現在的協程是一個概念了。
所以,這裡我們就不再講以前的那種應用內的線程,只講新的協程。
如果說到線程,就是新的可以被操作系統獨立調度的線程。
協程作為應用系統內調度的子任務單元,當然也是會共享進程的各種資源,除了自己的棧空間(函數調用、參數、返回值、局部變數)。
而協程與線程主要的區別有兩個,最大的就是調度方式,線程是操作系統調度,協程是應用系統自己調度。
另外一個區別,協程的棧空間是可以動態調整的,這樣空間利用率就可以更高,一個任務需要2K空間就分配2K內存,一個任務需要20M空間就分配20M,而不用擔心棧空間不夠或者空間浪費。
由於上面的兩個原因,協程的優勢也就凸顯出來。
1 協程可以更好的利用CPU,不用把CPU浪費在線程調度和上下文切換上。
2 協程可以更好的利用內存,不用全都分配一個偏大的空間,只需要分配需要的對應空間即可。
【隊列介紹】
這裡的隊列不是獨立的消息隊列服務,而只是應用中維持數據的一個隊列,很多時候會是一個數組或者鏈表。
隊列裡面保存的也不是一個子任務,而只是一個數據,具體這個數據拿出來之後要啟動什麼子任務,這個隊列是不關心的。
隊列只是一個緩衝帶,把更多的獨立數據先臨時保持住,應用系統有多大的能力消化吸收就從裡面用多快的速度進行處理。
從上面可以看出,隊列比協程還要簡單,都沒有所謂各自獨立的子任務,也就沒有了獨立的棧空間。
所以,這樣的簡化,也就帶來了更少的資源開銷,更少的任務調度。
接下來,我們結合實際中的幾種運行模式來介紹下現狀和發展。
【多進程:php fast-cgi】
php在使用fast-cgi之前,更多是多線程模式,為什麼轉而回到多進程模式呢?
多線程模式是為每個網路請求創建一個線程來處理這個請求,當請求執行結束,再銷毀這個線程。
於是,當網站的請求量高的時候,意味著反覆的為這些請求創建和銷毀線程,這個開銷就變得比較大,效率也就下降了。
在多進程模式下,進程是復用的,不會反覆的創建和銷毀,所以就沒有之前多線程模式那樣大的資源浪費了。
當然,多進程的問題就像上面說到的,內存開銷大,系統調度開銷大,所以也就意味著並發量相對就會比較小。
所以,新的php swoole框架也把協程引入進來,同時把多路復用的epoll網路模型引入進來,這樣就帶來了很明顯的好處。
1 協程佔用內存小,可以同時維持更多的並發請求。
2 epoll網路模型非阻塞而且系統開銷少,可以更好的利用CPU資源,同時避免了網路IO阻塞影響整體的任務執行。
【多線程:java web】
java多線程的運行模式用到線程池的技術,並不是每個請求都會啟動一個線程來處理,而是復用線程池中的線程,這樣也就類似上面php fast-cgi模式,很好的避免了線程頻繁創建和銷毀所帶來的損耗。
線程比進程更輕量,所以單個線程的內存佔用會比單個進程少,但是因為線程棧空間固定,在一些個別請求中,數據量很大,也可能會不得已要設置較大的棧空間,這樣一來,內存浪費也是會比較嚴重了。
在之前的文章《認識IO的問題才能更好的設計和開發出高並發和高性能的系統》,也有提到,java中更好支持IO密集型的框架,可以用netty,同樣是支持多路復用的epoll模型,也簡化了自己去實現NIO的過程。
kotlin.corouties 了解一下,簡化的JAVA,1.3版本會發布協程的正式線上支持。
【協程:go】
go原生的支持協程,並且有完善的協程調度器,讓協程在開發和運行時變得更加簡單和高效。
作為新的開發語言,普及還需要時間,在網路編程的系統中,還是非常有競爭力的。
一步到位的支持高並發和高性能,說的太多就怕它驕傲了(站在巨人肩膀上,新思維、新技術)。
【隊列:nginx】
nginx實際是一個master+多個worker,也算是多進程模式。但是work是單線程的,卻可以支持超高的網路並發量,這就是nginx內部實際就是一個網路事件隊列。
每個請求進來都是一個connection,然後這個connection就通過epoll_ctl註冊到系統的網路IO事件中,當connection的網路事件準備好了才通過回調函數放到已就緒隊列中。
而nginx就是epoll_wait不斷的輪詢這個就緒隊列,然後再處理這裡的事件。
網路請求的處理又有很多的階段,每個階段又可以有多個nginx模塊來處理,這些nginx模塊就是各個真正的任務處理系統。
nginx除了反向代理以及作為靜態WEB伺服器,也可以作為應用伺服器,比如利用ngx_lua模塊,就可以對WEB請求做實時動態的處理,來完成一個動態服務。
這樣一來,nginx把網路請求放到事件隊列中,ngx_lua利用協程把各個請求動態執行,也就可以高效的達到一個應用伺服器的效果了,而且並發、性能也非常好。
【總結】
從上面幾種模式中,我們都看到協程在新的框架、模塊中用的越來越多,而且也確實能非常明顯的提高系統的並發量。
而協程在20年前就已經提出來和運用,為什麼到這幾年才開始普及和應用開來呢?
這是一個發展的問題:
1 以前多核並行運算少,網路編程沒有這麼普遍(硬體);
2 以前留下來的代碼庫都不支持協程,重新開發難度大(軟體);
3 以前的程序員大部分都不知道協程,自然支持的也少(人)。
同樣的,多路復用epoll網路模型也在越來越多的系統中被使用,非阻塞帶來高效的同時,還可以同步方式編碼,所以,現在的程序員技術庫武器越來越強大,開發出來的系統自然也不會太弱了。
在實戰課程 《PHP秒殺系統 高並發高性能的極致挑戰》中,也是針對這類高並發的業務場景做了特定的性能優化以及分散式方案,大家可以參考學習。
作者:一凡Sir
鏈接:https://www.imooc.com/article/31751
來源:慕課網
本文原創發佈於慕課網 ,轉載請註明出處,謝謝合作
推薦閱讀:
【重磅】認證作者招募 | 打造個人品牌 so easy !
有獎徵文004期|從小白到大牛,進階路上有話說?
基於Spring 4.0 的 Web Socket 聊天室/遊戲服務端簡單架構
適合Python 新手的5大練手項目,你練了么?
2018 程序員的末日來了?到了「窮途末路」嗎?(上)
推薦閱讀:
※cort_proto v0.9.0 第一版發布
※Go並發調度器解析之實現一個協程池
※golang簡單key/value資料庫(二)
※遲來的HTTP2簡明教程