這是一個建立於
的文章,其中的資訊可能已經有所發展或是發生改變。
高並發編程裡多線程(進程)的弊端
其實從著名的 C10K 問題的時候,就談到了高並發編程時,採用多線程(或進程)是一種不可取的解決方案,核心原因是因為線程(或進程)本質上都是作業系統的資源,每個線程需要額外佔用1M或者2M的記憶體空間,所以2G記憶體,能承受的線程數差不多隻能到1k這個量級。
而且線程的調度由作業系統調度,當線程或者進程數到達一定量級的時候,據有人實驗的結果是並發的線程數到達1k以上後,作業系統基本上就已經不堪重負,調度不過來了。
事件驅動
已知多線程已經無法解決高並發問題,所以才有了非同步IO,事件驅動等概念來解決高並發編程。很典型的就是 Node.js ,傳說中的事件驅動,其實就是在底層使用了 libuv然後通過各種回呼函數來註冊事件,當事件觸發的時候回呼函數也被觸發。使用事件驅動的方式確實能解決高並發的問題,但是因為事件驅動最費勁的就是各種喪心病狂不停的回調,在回調裡面再嵌套回調,容易陷入所謂的【回調地獄】。這也是 Node.js 最受人詬病的地方。
高並發解決方案之協程
物件導向最典型的語言是 Java ,事件驅動最典型的語言是 Node.js ,協程最典型的語言就是 golang ,當然國內程式員響馬的 fibjs 也是基於協程來進行並發的,也非常出色,只不過在生態上還是發展的太慢了。個人看來協程相對於事件驅動是一種更先進的高並發解決方案,把複雜的邏輯和非同步都封裝在底層,讓程式員在編程時感覺不到非同步存在,用響馬的話就是【用同步抒寫非同步情懷】。個人很看好協程的發展,同時也非常看好 golang 的前景,
協程也叫使用者級線程,很多人分不清楚協程和線程和進程的關係。簡單的說就是:線程和進程的調度是由作業系統來調控,而協程的調度由使用者自己調控。所以協程調度器可以在協程A即將進入阻塞IO操作,比如 socket 的 read (其實已經設定為非同步IO )之前,將該協程掛起,把當前的棧資訊 StackA 儲存下來,然後切換到協程B,等到協程A的該 IO操作返回時,再根據 StackA 切回到之前的協程A當時的狀態。
ucontext
雲風 開源過一個簡單的協程實現 cloudwu-coroutine,主要是基於 ucontext 來進行狀態的切換。
ucontext 由以下四個函數組成:
- getcontext
- setcontext
- makecontext
- swapcontext
具體含義Google一下看看例子就明白了,資料很多。說白了其實就是一種類似 goto 的編程方法,getcontext 就是將當前協程上下文獲得並存起來,setcontext 就是跳轉到某個協程上下文狀態。前者類似 label , 後者類似 goto 。makecontext 和 swapcontext 稍微再複雜一點點,但是也都是乾著環境切換的勾當。
雲風 的 cloudwu-coroutine 代碼非常乾淨漂亮,學習之餘也 fork 並修改了一份雲風的協程實現:fork-coroutine ,沒什麼高明的改進,只是對我來說更加簡單易懂。