Lua 協同程式(coroutine)
什麼是協同(coroutine)?
Lua 協同程式(coroutine)與線程比較類似:擁有獨立的堆疊,獨立的局部變數,獨立的指令指針,同時又與其它協同程式共用全局變數和其他大部分東西。
協同是非常強大的功能,但是用起來也很複雜。
線程和協同程式區別
線程與協同程式的主要區別在於,一個具有多個線程的程式可以同時運行幾個線程,而協同程式卻需要彼此協作的運行。
在任一指定時刻只有一個協同程式在運行,並且這個正在運行的協同程式只有在明確的被要求掛起的時候才會被掛起。
協同程式有點類似同步的多線程,在等待同一個線程鎖的幾個線程有點類似協同。
基本語法
方法 | 描述 |
---|---|
coroutine.create() | 創建 coroutine,返回 coroutine, 參數是一個函數,當和 resume 配合使用的時候就喚醒函數調用 |
coroutine.resume() | 重啟 coroutine,和 create 配合使用 |
coroutine.yield() | 掛起 coroutine,將 coroutine 設置為掛起狀態,這個和 resume 配合使用能有很多有用的效果 |
coroutine.status() |
查看 coroutine 的狀態 注:coroutine 的狀態有三種:dead,suspended,running,具體什麼時候有這樣的狀態請參考下麵的程式 |
coroutine.wrap() | 創建 coroutine,返回一個函數,一旦你調用這個函數,就進入 coroutine,和 create 功能重複 |
coroutine.running() | 返回正在跑的 coroutine,一個 coroutine 就是一個線程,當使用running的時候,就是返回一個 corouting 的線程號 |
以下實例演示了以上各個方法的用法:
coroutine_test.lua 檔
-- coroutine_test.lua 檔
co = coroutine.create(
function(i)
print(i);
end
)
coroutine.resume(co, 1) -- 1
print(coroutine.status(co)) -- dead
print("----------")
co = coroutine.wrap(
function(i)
print(i);
end
)
co(1)
print("----------")
co2 = coroutine.create(
function()
for i=1,10 do
print(i)
if i == 3 then
print(coroutine.status(co2)) --running
print(coroutine.running()) --thread:XXXXXX
end
coroutine.yield()
end
end
)
coroutine.resume(co2) --1
coroutine.resume(co2) --2
coroutine.resume(co2) --3
print(coroutine.status(co2)) -- suspended
print(coroutine.running())
print("----------")
co = coroutine.create(
function(i)
print(i);
end
)
coroutine.resume(co, 1) -- 1
print(coroutine.status(co)) -- dead
print("----------")
co = coroutine.wrap(
function(i)
print(i);
end
)
co(1)
print("----------")
co2 = coroutine.create(
function()
for i=1,10 do
print(i)
if i == 3 then
print(coroutine.status(co2)) --running
print(coroutine.running()) --thread:XXXXXX
end
coroutine.yield()
end
end
)
coroutine.resume(co2) --1
coroutine.resume(co2) --2
coroutine.resume(co2) --3
print(coroutine.status(co2)) -- suspended
print(coroutine.running())
print("----------")
以上實例執行輸出結果為:
1 dead ---------- 1 ---------- 1 2 3 running thread: 0x7fb801c05868 false suspended thread: 0x7fb801c04c88 true ----------
coroutine.running就可以看出來,coroutine在底層實現就是一個線程。
當create一個coroutine的時候就是在新線程中註冊了一個事件。
當使用resume觸發事件的時候,create的coroutine函數就被執行了,當遇到yield的時候就代表掛起當前線程,等候再次resume觸發事件。
接下來我們分析一個更詳細的實例:
實例
function foo (a)
print("foo 函數輸出", a)
return coroutine.yield(2 * a) -- 返回 2*a 的值
end
co = coroutine.create(function (a , b)
print("第一次協同程式執行輸出", a, b) -- co-body 1 10
local r = foo(a + 1)
print("第二次協同程式執行輸出", r)
local r, s = coroutine.yield(a + b, a - b) -- a,b的值為第一次調用協同程式時傳入
print("第三次協同程式執行輸出", r, s)
return b, "結束協同程式" -- b的值為第二次調用協同程式時傳入
end)
print("main", coroutine.resume(co, 1, 10)) -- true, 4
print("--分割線----")
print("main", coroutine.resume(co, "r")) -- true 11 -9
print("---分割線---")
print("main", coroutine.resume(co, "x", "y")) -- true 10 end
print("---分割線---")
print("main", coroutine.resume(co, "x", "y")) -- cannot resume dead coroutine
print("---分割線---")
print("foo 函數輸出", a)
return coroutine.yield(2 * a) -- 返回 2*a 的值
end
co = coroutine.create(function (a , b)
print("第一次協同程式執行輸出", a, b) -- co-body 1 10
local r = foo(a + 1)
print("第二次協同程式執行輸出", r)
local r, s = coroutine.yield(a + b, a - b) -- a,b的值為第一次調用協同程式時傳入
print("第三次協同程式執行輸出", r, s)
return b, "結束協同程式" -- b的值為第二次調用協同程式時傳入
end)
print("main", coroutine.resume(co, 1, 10)) -- true, 4
print("--分割線----")
print("main", coroutine.resume(co, "r")) -- true 11 -9
print("---分割線---")
print("main", coroutine.resume(co, "x", "y")) -- true 10 end
print("---分割線---")
print("main", coroutine.resume(co, "x", "y")) -- cannot resume dead coroutine
print("---分割線---")
以上實例執行輸出結果為:
第一次協同程式執行輸出 1 10 foo 函數輸出 2 main true 4 --分割線---- 第二次協同程式執行輸出 r main true 11 -9 ---分割線--- 第三次協同程式執行輸出 x y main true 10 結束協同程式 ---分割線--- main false cannot resume dead coroutine ---分割線---
以上實例接下如下:
- 調用resume,將協同程式喚醒,resume操作成功返回true,否則返回false;
- 協同程式運行;
- 運行到yield語句;
- yield掛起協同程式,第一次resume返回;(注意:此處yield返回,參數是resume的參數)
- 第二次resume,再次喚醒協同程式;(注意:此處resume的參數中,除了第一個參數,剩下的參數將作為yield的參數)
- yield返回;
- 協同程式繼續運行;
- 如果使用的協同程式繼續運行完成後繼續調用 resume方法則輸出:cannot resume dead coroutine
resume和yield的配合強大之處在於,resume處於主程中,它將外部狀態(數據)傳入到協同程式內部;而yield則將內部的狀態(數據)返回到主程中。
生產者-消費者問題
現在我就使用Lua的協同程式來完成生產者-消費者這一經典問題。
實例
local newProductor
function productor()
local i = 0
while true do
i = i + 1
send(i) -- 將生產的物品發送給消費者
end
end
function consumer()
while true do
local i = receive() -- 從生產者那裏得到物品
print(i)
end
end
function receive()
local status, value = coroutine.resume(newProductor)
return value
end
function send(x)
coroutine.yield(x) -- x表示需要發送的值,值返回以後,就掛起該協同程式
end
-- 啟動程式
newProductor = coroutine.create(productor)
consumer()
function productor()
local i = 0
while true do
i = i + 1
send(i) -- 將生產的物品發送給消費者
end
end
function consumer()
while true do
local i = receive() -- 從生產者那裏得到物品
print(i)
end
end
function receive()
local status, value = coroutine.resume(newProductor)
return value
end
function send(x)
coroutine.yield(x) -- x表示需要發送的值,值返回以後,就掛起該協同程式
end
-- 啟動程式
newProductor = coroutine.create(productor)
consumer()
以上實例執行輸出結果為:
1 2 3 4 5 6 7 8 9 10 11 12 13 ……