今天我們先來看下lua手冊上一個協程實例:
手冊實例:
function foo(a)
print("foo", a)
return coroutine.yield(2 * a)
end
co = coroutine.create(function ( a, b )
print("co-body", a, b)
local r = foo(a + 1)
print("co-body", r)
local r, s = coroutine.yield(a + b, a - b)
print("co-body", r, s)
return b, "end"
end)
print("main", coroutine.resume(co, 1, 10))
print("main", coroutine.resume(co, "r"))
print("main", coroutine.resume(co, "x", "y"))
print("main", coroutine.resume(co, "x", "y"))
執行結果:
co-body 1 10 -- 協程co的第7行,此時resume()傳入的參數是賦值給了函數的
foo 2 -- 在第8行裏面調用了函數foo(),執行到第2行的打印
main true 4 -- 由於函數foo()的第3行yield()執行後掛起,參數是4,作為第15行的resume()的第二個返回值,最終打印了出來,到此,第15行執行完畢
co-body r -- 第16行resume()再次喚醒協程co,接着上次yield()的地方繼續執行,參數“r"被賦值給上次yield()的返回值,在第9行打印出來
main true 11 -9 -- 在第10行yiled()後再次掛起協程co,並返回,此時參數a和b還是第一次resume()時的參數,1,10,所以yield()兩個參數分別為11,-9,作為resum()的第二個返回值,最終被打印出來,到此,第16行執行完畢
co-body x y -- 第17行resume()再次喚醒協程co,傳入的參數“x”,“y”被賦值給上次的yield()函數的返回值,即賦值給第10行的r,s,在第11行被打印出來
main true 10 end -- 協程co在第12行返回,注意此時參數b仍然是第一次resume()時的參數2,值為10,至此協程co執行結束,變為dead狀態,最終在第17行打印出來
main false cannot resume dead coroutine -- 第18行嘗試再次resume()協程co,由於協程co已經為dead狀態,所以直接返回並報錯
上面這個實例很好的展示了coroutine.yield和coroutine.resume之間的相互作用。協程在生產者和消費者問題上應用也比較廣泛,我們來看看下面這個例子。
生產者與消費者:
coConsume = coroutine.create(
function ()
while true do
local stutas, msg = coroutine.resume(coProducer)
print('receive msg : ', msg)
coroutine.resume(coProducer, string.len(msg))
end
end
)
coProducer = coroutine.create(
function ()
while true do
local msg = io.read()
local len = coroutine.yield(msg)
print('he tell me he has recved data len is ', len)
coroutine.yield()
end
end
)
coroutine.resume(coConsume)
運行結果:
hello --從鍵盤輸入
receive msg : hello
he tell me he has recved data len is 5
上面這個實例有兩個協程,一個是生產協程主要是從屏幕上接收輸入的數據,另外一個是消費協程,處理接收到的信息將其打印。
總結:
最後引用知乎上的一段話作為協程學習的一個小結。
在IO密集型的程序中由於IO操作遠遠小於CPU的操作,所以往往需要CPU去等IO操作。同步IO下系統需要切換線程,讓操作系統可以在IO過程中執行其他的東西。這樣雖然代碼是符合人類的思維習慣但是由於大量的線程切換帶來了大量的性能的浪費,尤其是IO密集型的程序。
所以人們發明了異步IO。就是當數據到達的時候觸發我的回調。來減少線程切換帶來性能損失。但是這樣的壞處也是很大的,主要的壞處就是操作被 “分片” 了,代碼寫的不是 “一氣呵成” 這種。 而是每次來段數據就要判斷 數據夠不夠處理哇,夠處理就處理吧,不夠處理就在等等吧。這樣代碼的可讀性很低,其實也不符合人類的習慣。
但是協程可以很好解決這個問題,比如 把一個IO操作 寫成一個協程。當觸發IO操作的時候就自動讓出CPU給其他協程。要知道協程的切換很輕的。協程通過這種對異步IO的封裝 既保留了性能也保證了代碼的 容易編寫和可讀性。在高IO密集型的程序下很好,但是高CPU密集型的程序下沒啥好處。