一個故事講完進程、線程和協(xié)程
很久以前,有兩個程序,暫且稱他們旺財和小強吧。
旺財和小強這兩個程序都很長,每個都有十幾萬行。 他們兩個的人生價值就是到CPU上去運行,把運行結(jié)果告訴人類。
CPU是稀缺資源,只有一個,他們倆必須排著隊,輪流使用。
旺財從頭到尾執(zhí)行完了,讓出CPU, 讓小強從頭兒去執(zhí)行。
人類把這種處理方式叫做批處理。
進程
長久以來,兩人相安無事。 后來CPU的速度越來越快, 遠遠超過了內(nèi)存,硬盤的速度。
人類想到,這批處理系統(tǒng)的效率有點低啊,你看當小強需要從硬盤上讀取數(shù)據(jù)的時候,CPU也一直在等待,這是多大的浪費??!這時候完全可以讓旺財來運行一下嘛!
當然得保存好小強的執(zhí)行現(xiàn)場:具體執(zhí)行到那一行程序指令了, 函數(shù)調(diào)用到什么層次了,每個函數(shù)調(diào)用都有什么樣的參數(shù),CPU寄存器中的值..... 等等一系列東西。
如果不把小強的執(zhí)行現(xiàn)場給保存下來,等到小強的數(shù)據(jù)從銀盤讀完了,就沒法回到中斷處來繼續(xù)執(zhí)行了。
這個執(zhí)行現(xiàn)場,再加上小強的代碼,就是一個執(zhí)行中的程序,被稱為“進程” 。
旺財和小強在運行的時候,也被改造成了進程。
人類還規(guī)定:進程不能長時間占據(jù)CPU, 只能在CPU上執(zhí)行一小會兒,然后馬上切換到別的進程去執(zhí)行。
旺財和小強不以為意:不就是執(zhí)行一會兒,歇一會兒,然后繼續(xù)執(zhí)行嘛!
但是他們不知道的是,由于CPU運行速度超快,旺財和小強雖然在不斷地切換運行,在人類那緩慢的世界里看來,旺財和小強好像是同時在執(zhí)行一樣。 這就是并發(fā)。
(在人類看來,小強和旺財似乎是在同時執(zhí)行)
多年以后,他們倆才真正地實現(xiàn)了并行: 在一個豪華電腦中,每人都被分配了一個CPU , 真正地同時執(zhí)行, 這是后話了。
線程
這時候旺財已經(jīng)有了界面,還能訪問網(wǎng)絡,每當它聯(lián)網(wǎng)的時候(這也是個非常非常耗時的操作),就得把CPU讓給小強。
即使旺財再次被調(diào)度執(zhí)行,由于網(wǎng)絡數(shù)據(jù)還沒有返回,他必須等待,什么事情都做不了,在人類看來,界面根本無法操作,旺財不響應了! 氣得人類經(jīng)常把旺財kill掉。
旺財心里苦,他很納悶小強怎么就沒有問題,小強不是要讀寫硬盤嗎? 那也是很慢的操作啊。
小強說:“你傻啊,內(nèi)部只有一個執(zhí)行的流程,一遇到耗時的操作就得等待,你看看我,內(nèi)部搞了兩個執(zhí)行流程(線程),一個用來讀寫硬盤(T1),另外一個處理界面(T2)。我和操作系統(tǒng)商量好了,如果T1在讀寫硬盤, 就可以調(diào)度我的T2來執(zhí)行,這樣界面至少還可以操作。 ”
旺財覺得很有意思,也采用了類似辦法。
于是,一個進程中至少有一個執(zhí)行的流程(主線程),也可以開啟新的執(zhí)行流程(線程)。
線程變成了最小的調(diào)度單位。
協(xié)程
這一天,旺財被一個叫做生產(chǎn)者和消費者的問題折騰地死去活來,兩個線程,一個線程向隊列中放數(shù)據(jù),另外一個從隊列中取數(shù)據(jù),處理起兩個線程的協(xié)作就顯得很麻煩,不但需要加鎖,還得做好線程的通知和等待。
正在感慨多線程編程之難的時候, 旺財震驚地發(fā)現(xiàn),小強用了一個極為簡單的辦法把生產(chǎn)者,消費者問題給解決了。
這個方法的代碼如下:
- # 生產(chǎn)者
- def producer(c):
- #其他代碼
- while True:
- value = ...生成數(shù)據(jù)...
- c.send(value)
- # 消費者
- def consumer():
- #其他代碼
- while True:
- value = yield
- print(value)
- c = consumer()
- producer(c)
“這....這怎么執(zhí)行啊,那個yield是怎么回事?” 旺財表示不解。
“簡單啊,你看那個生產(chǎn)者,是不是向消費者發(fā)送了數(shù)據(jù)? ” 小強說。
“對啊,然后呢,生產(chǎn)者發(fā)送了數(shù)據(jù)以后,會馬上進行下一輪循環(huán)嗎?”
“這就是關(guān)鍵所在了,”小強說,“ 它們是這么執(zhí)行的:”
- 生產(chǎn)者發(fā)送數(shù)據(jù),暫停運行,不進行下一輪循環(huán)
- 消費者其實一直在value = yield 那里等待,直到數(shù)據(jù)到來,現(xiàn)在數(shù)據(jù)來了,取出處理(value就是生產(chǎn)者發(fā)送過來的數(shù)據(jù))。
- 消費者在循環(huán)中再次yield, 暫停執(zhí)行。
- 生產(chǎn)者繼續(xù)下一輪的循環(huán),生成新的消息,發(fā)送給消費者。
旺財覺得很吃驚,小強竟然可以讓一個正在執(zhí)行的程序暫停,他不由得問道:“你這個暫停是真的停止了了,還是說只是像Java的yield那樣,讓出CPU進入了就緒狀態(tài)? 等待下次調(diào)度運行?”
“是真的暫停了,程序就停在那里,等待運行控制權(quán)從對方那里轉(zhuǎn)移過來。”
“這不是操作系統(tǒng)干的事情嗎? ” 旺財更加吃驚了。
“正是這樣,” 小強得意地說:“我打算把類似生產(chǎn)者,消費者這樣的代碼稱為‘協(xié)程’, 這個協(xié)程有個重要的特點,就是完全被我所調(diào)度和掌控, 不用操作系統(tǒng)介入。”
“這個協(xié)程和線程似乎很像啊。每次協(xié)程停止執(zhí)行的時候,也得保存現(xiàn)場,要不然沒法恢復執(zhí)行。” 旺財說。
“是啊,只是他們比線程更加輕量級,操作系統(tǒng)內(nèi)核不用參與,相當于用戶態(tài)線程了,協(xié)程的開銷極小,可以輕松地創(chuàng)建大量的協(xié)程來做事情。 對了,也許你注意到了,我這兩個協(xié)程是'合作式'的,它們兩個同一時刻只能有一個在運行。 實際上,我在底層可以用一個線程去執(zhí)行這兩個協(xié)程。 ”
旺財表示同意:“不錯,既然兩個程序可以'合作',那就不用加鎖了,也不用在代碼里寫什么wait和notify了,在程序?qū)用?,可以用同步的方式實現(xiàn)異步的功能了! 代碼很清晰,我也搞個協(xié)程來玩玩吧!”
【本文為51CTO專欄作者“劉欣”的原創(chuàng)稿件,轉(zhuǎn)載請通過作者微信公眾號coderising獲取授權(quán)】


































