Lua 協同程序(coroutine)



什麼是協同(coroutine)?

Lua 協同程式(coroutine)與執行緒比較類似:擁有獨立的堆疊,獨立的局部變量,獨立的指令指針,同時與其它協同程式共享全域變數和其它大部分東西。

協同是非常強大的功能,但是用起來也很複雜。

線程和協同程序區別

線程與協同程序的主要區別在於,一個具有多個線程的程式可以同時運行幾個線程,而協同程式卻需要彼此協作的運行。

在任一指定時刻只有一個協同程式在執行,而這個正在執行的協同程式只有在明確的被要求掛起的時候才會被掛起。

協同程式有點類似同步的多線程,在等待同一個線程鎖定的幾個線程有點類似協同。

基本語法

方法說明
coroutine.create()建立coroutine,返回coroutine,參數是一個函數,當和resume配合使用的時候就喚醒函數呼叫
coroutine.resume()#重啟coroutine,和create配合使用
coroutine.yield()掛起coroutine,將coroutine設定為掛起狀態,這個和resume配合使用能有很多有用的效果
coroutine.status()查看coroutine的狀態
註:coroutine的狀態有三種:dead,suspend,running,具體什麼時候有這樣的狀態請參考下面的程式
coroutine.wrap()建立coroutine,回傳一個函數,一旦你呼叫這個函數,就進入coroutine,和create功能重複
coroutine.running()返回正在跑的coroutine,一個coroutine就是一個線程,當使用running的時候,就是回傳一個corouting的線程號

以下實例示範了以上各個方法的用法:

-- 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("----------")

以上實例執行輸出結果為:

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("---分割线---")

以上實例執行輸出結果為:

第一次协同程序执行输出	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()
###以上執行個體執行輸出結果為:###
1
2
3
4
5
6
7
8
9
10
11
12
13
……
###