Lua中的協同程式 coroutine
Lua中的協程和多執行緒很相似,每一個協程有自己的堆疊,自己的區域性變數,可以通過yield-resume實現在協程間的切換。不同之處是:Lua協程是非搶佔式的多執行緒,必須手動在不同的協程間切換,且同一時刻只能有一個協程在執行。並且Lua中的協程無法在外部將其停止,而且有可能導致程式阻塞。
協同程式(Coroutine):
三個狀態:suspended(掛起,協同剛建立完成時或者yield之後)、running(執行)、dead(函式走完後的狀態,這時候不能再重新resume)。
coroutine.create(arg):根據一個函式建立一個協同程式,引數為一個函式
coroutine.resume(co):使協同從掛起變為執行(1)啟用coroutine,也就是讓協程函式開始執行;(2)喚醒yield,使掛起的協同接著上次的地方繼續執行。該函式可以傳入引數
coroutine.status(co):檢視協同狀態
coroutine.yield():使正在執行的協同掛起,可以傳入引數
resume函式的兩種用途雖然都是使協同掛起,但還是有些許差異的,看下面這個例子:
coroutineFunc = function (a, b) for i = 1, 10 do print(i, a, b) coroutine.yield() end end co2 = coroutine.create(coroutineFunc) --建立協同程式co2 coroutine.resume(co2, 100, 200) -- 1 100 200 開啟協同,傳入引數用於初始化 coroutine.resume(co2) -- 2 100 200 coroutine.resume(co2, 500, 600) -- 3 100 200 繼續協同,傳入引數無效 co3 = coroutine.create(coroutineFunc) --建立協同程式co3 coroutine.resume(co3, 300, 400) -- 1 300 400 開啟協同,傳入引數用於初始化 coroutine.resume(co3) -- 2 300 400 coroutine.resume(co3) -- 3 300 400
Lua中協同的強大能力,還在於通過resume-yield來交換資料:
(1)resume把引數傳給程式(相當於函式的引數呼叫);
(2)資料由yield傳遞給resume;
(3)resume的引數傳遞給yield;
(4)協同程式碼結束時的返回值,也會傳給resume
協同中的引數傳遞形勢很靈活,一定要注意區分,在啟動coroutine的時候,resume的引數是傳給主程式的;在喚醒yield的時候,引數是傳遞給yield的。看下面這個例子:
co = coroutine.create(function (a, b) print("co", a, b, coroutine.yield()) end) coroutine.resume(co, 1, 2) --沒輸出結果,注意兩個數字引數是傳遞給函式的 coroutine.resume(co, 3, 4, 5) --co 1 2 3 4 5,這裡的兩個數字引數由resume傳遞給yield
Lua的協同稱為不對稱協同(asymmetric coroutines),指“掛起一個正在執行的協同函式”與“使一個被掛起的協同再次執行的函式”是不同的,有些語言提供對稱協同(symmetric coroutines),即使用同一個函式負責“執行與掛起間的狀態切換”。
注意:resume執行在保護模式下,因此,如果協同程式內部存在錯誤,Lua並不會丟擲錯誤,而是將錯誤返回給resume函式。
以下是我個人的一點理解:
(1)resume可以理解為函式呼叫,並且可以傳入引數,啟用協同時,引數是傳給程式的,喚醒yield時,引數是傳遞給yield的;
(2)yield就相當於是一個特殊的return語句,只是它只是暫時性的返回(掛起),並且yield可以像return一樣帶有返回引數,這些引數是傳遞給resume的。
為了理解上面兩句話的含義,我們來看一下如何利用Coroutine來解決生產者——消費者問題的簡單實現:
produceFunc = function()
while true do
local value = io.read()
print("produce: ", value)
coroutine.yield(value) --返回生產的值
end
end
consumer = function(p)
while true do
local status, value = coroutine.resume(p); --喚醒生產者進行生產
print("consume: ", value)
end
end
--消費者驅動的設計,也就是消費者需要產品時找生產者請求,生產者完成生產後提供給消費者
producer = coroutine.create(produceFunc)
consumer(producer)
這是一種消費者驅動的設計,我們可以看到resume操作的結果是等待一個yield的返回,這很像普通的函式呼叫,有木有。我們還可以在生產消費環節之間加入一箇中間處理的環節(過濾器):
produceFunc = function()
while true do
local value = io.read()
print("produce: ", value)
coroutine.yield(value) --返回生產的值
end
end
filteFunc = function(p)
while true do
local status, value = coroutine.resume(p);
value = value *100 --放大一百倍
coroutine.yield(value)
end
end
consumer = function(f, p)
while true do
local status, value = coroutine.resume(f, p); --喚醒生產者進行生產
print("consume: ", value)
end
end
--消費者驅動的設計,也就是消費者需要產品時找生產者請求,生產者完成生產後提供給消費者
producer = coroutine.create(produceFunc)
filter = coroutine.create(filteFunc)
consumer(filter, producer)
可以看到,我們在中間過濾器中將生產出的值放大了一百倍。
通過這個例子應該很容易理解coroutine中如何利用resume-yield呼叫來進行值傳遞了,他們像“呼叫函式——返回值”一樣的工作,也就是說resume像函式呼叫一樣使用,yield像return語句一樣使用。coroutine的靈活性也體現在這種通過resume-yield的值傳遞上。