1. 程式人生 > >一個故事講完程序、執行緒和協程

一個故事講完程序、執行緒和協程

很久以前,有兩個程式,暫且稱他們旺財和小強吧。

旺財和小強這兩個程式都很長,每個都有十幾萬行。 他們兩個的人生價值就是到CPU上去執行,把執行結果告訴人類。

CPU是稀缺資源,只有一個,他們倆必須排著隊,輪流使用。

旺財從頭到尾執行完了,讓出CPU, 讓小強從頭兒去執行。

人類把這種處理方式叫做批處理

程序

長久以來,兩人相安無事。 後來CPU的速度越來越快, 遠遠超過了記憶體,硬碟的速度。

人類想到,這批處理系統的效率有點低啊,你看當小強需要從硬碟上讀取資料的時候,CPU也一直在等待,這是多大的浪費啊!這時候完全可以讓旺財來執行一下嘛!

當然得儲存好小強的執行現場:具體執行到那一行程式指令了, 函式呼叫到什麼層次了,每個函式呼叫都有什麼樣的引數,CPU暫存器中的值..... 等等一系列東西。

如果不把小強的執行現場給儲存下來,等到小強的資料從銀盤讀完了,就沒法回到中斷處來繼續執行了。

這個執行現場,再加上小強的程式碼,就是一個執行中的程式,被稱為“程序” 。

旺財和小強在執行的時候,也被改造成了程序。

人類還規定:程序不能長時間佔據CPU, 只能在CPU上執行一小會兒,然後馬上切換到別的程序去執行。

旺財和小強不以為意:不就是執行一會兒,歇一會兒,然後繼續執行嘛!

但是他們不知道的是,由於CPU執行速度超快,旺財和小強雖然在不斷地切換執行,在人類那緩慢的世界裡看來,旺財和小強好像是同時在執行一樣。  這就是併發

(在人類看來,小強和旺財似乎是在同時執行)

多年以後,他們倆才真正地實現了並行: 在一個豪華電腦中,每人都被分配了一個CPU , 真正地同時執行, 這是後話了。

執行緒

這時候旺財已經有了介面,還能訪問網路,每當它聯網的時候(這也是個非常非常耗時的操作),就得把CPU讓給小強。

即使旺財再次被排程執行,由於網路資料還沒有返回,他必須等待,什麼事情都做不了,在人類看來,介面根本無法操作,旺財不響應了!  氣得人類經常把旺財kill掉。

旺財心裡苦,他很納悶小強怎麼就沒有問題,小強不是要讀寫硬碟嗎? 那也是很慢的操作啊。

小強說:“你傻啊,內部只有一個執行的流程,一遇到耗時的操作就得等待,你看看我,內部搞了兩個執行流程(執行緒),一個用來讀寫硬碟(T1),另外一個處理介面(T2)。我和作業系統商量好了,如果T1在讀寫硬碟, 就可以排程我的T2來執行,這樣介面至少還可以操作。 ”

旺財覺得很有意思,也採用了類似辦法。

於是,一個程序中至少有一個執行的流程(主執行緒),也可以開啟新的執行流程(執行緒)。

執行緒變成了最小的排程單位。

協程

這一天,旺財被一個叫做生產者和消費者的問題折騰地死去活來,兩個執行緒,一個執行緒向佇列中放資料,另外一個從佇列中取資料,處理起兩個執行緒的協作就顯得很麻煩,不但需要加鎖,還得做好執行緒的通知和等待。

正在感慨多執行緒程式設計之難的時候, 旺財震驚地發現,小強用了一個極為簡單的辦法把生產者,消費者問題給解決了。

這個方法的程式碼如下:

# 生產者
def producer(c):  
   #其他程式碼  
   while True:          
       value = ...生成資料...
       c.send(value)

# 消費者
def consumer():    
   #其他程式碼      
   while True:
       value = yield
       print(value)

c = consumer()
producer(c)

“這....這怎麼執行啊,那個yield是怎麼回事?”  旺財表示不解。

“簡單啊,你看那個生產者,是不是向消費者傳送了資料? ” 小強說。

“對啊,然後呢,生產者傳送了資料以後,會馬上進行下一輪迴圈嗎?”

“這就是關鍵所在了,”小強說,“ 它們是這麼執行的:”

  1. 生產者傳送資料,暫停執行,不進行下一輪迴圈

  2. 消費者其實一直在value = yield 那裡等待,直到資料到來,現在資料來了,取出處理(value就是生產者傳送過來的資料)。

  3. 消費者在迴圈中再次yield, 暫停執行。

  4. 生產者繼續下一輪的迴圈,生成新的訊息,傳送給消費者。

旺財覺得很吃驚,小強竟然可以讓一個正在執行的程式暫停,他不由得問道:“你這個暫停是真的停止了了,還是說只是像Java的yield那樣,讓出CPU進入了就緒狀態? 等待下次排程執行?”

“是真的暫停了,程式就停在那裡,等待執行控制權從對方那裡轉移過來。”

“這不是作業系統乾的事情嗎? ” 旺財更加吃驚了。

“正是這樣,” 小強得意地說:“我打算把類似生產者,消費者這樣的程式碼稱為‘協程’, 這個協程有個重要的特點,就是完全被我所排程和掌控, 不用作業系統介入。”

“這個協程和執行緒似乎很像啊。每次協程停止執行的時候,也得儲存現場,要不然沒法恢復執行。” 旺財說。

“是啊,只是他們比執行緒更加輕量級,作業系統核心不用參與,相當於使用者態執行緒了,協程的開銷極小,可以輕鬆地建立大量的協程來做事情。 對了,也許你注意到了,我這兩個協程是'合作式'的,它們兩個同一時刻只能有一個在執行。 實際上,我在底層可以用一個執行緒去執行這兩個協程。  ”

旺財表示同意:“不錯,既然兩個程式可以'合作',那就不用加鎖了,也不用在程式碼裡寫什麼wait和notify了,在程式層面,可以用同步的方式實現非同步的功能了! 程式碼很清晰,我也搞個協程來玩玩吧!”