Lua 協程coroutine

  協程和通常多線程的區別是,通常多線程由系統決定該哪一個線程執行,是搶佔式的,而協程是由每一個線程本身決定本身何時不執行,並把執行權主動交給下一個線程。 協程是用戶空間線程,操做系統其存在一無所知,因此須要用戶本身去作調度,用來執行協做式多任務很是合適。

Lua所支持的協程全稱被稱做協同式多線程(collaborative multithreading)。Lua爲每一個coroutine提供一個獨立的運行線路。然而和多線程不一樣的地方就是,coroutine只有在顯式調用yield函數後才被掛起,同一時間內只有一個協程正在運行。python

Lua將它的協程函數都放進了coroutine這個表裏,其中主要的函數以下數組

                                         

建立一個協程須要調用coroutine.create 。它只接收單個參數,這個參數是 coroutine 的主函數。 create 函數僅僅建立一個新的coroutine 而後返回一個類型爲thread的對象,並不會啓動 coroutine 的運行。
>hxc = coroutine.create(function () print("hi coroutine") end)
>print(type(hxc))                     -->thread
>print(coroutine.status(hxc))     -->suspended
>coroutine.resume(co)              -->  hi coroutine ;函數coroutine.resume使協同程序由掛起狀態變爲運行態,執行完畢協程進入dead狀態
>print(coroutine.status(hxc))     -->dead

調 用 coroutine.resume 時,傳入的第一個參數就是 coroutine.create 的返回值。這時,coroutine 從主函數的第一行開始運行。接下來傳入 coroutine.resume 的參數將被傳進 coroutine 的主函數。在 coroutine 開始運行後,運行到自身終止或是遇到一個 yield調用,這個yield函數是協程特別之處,它能夠將正在運行的代碼掛起。
hxc = coroutine.create(function ()
    for i=1,10 do
       print("iter", i)
       coroutine.yield()
    end
end)
執行這個協同程序,程序將在第一個yield處被掛起:
coroutine.resume(hxc)         --> iter 1
print(coroutine.status(hxc))  --> suspended
執行
coroutine.resume(hxc)         --> iter 2;resume激活被掛起的程序,將從函數yield的位置繼續執行程序,直到再次遇到yield或程序結束。

Lua中協同能夠經過resume-yield來交換數據。
1)經過resume把參數傳遞給協同的主程序。
hxc = coroutine.create(function (a,b)
    print("hxc", a,b,c)
end)
coroutine.resume(hxc, 1, 2)                  --> hxc 1 2
2)數據經過yield傳給resume。true代表調用成功,true以後的部分,便是yield的參數。
hxc = coroutine.create(function(a,b)
    coroutine.yield(a + b, a - b)
end)
print(coroutine.resume(hxc, 20, 10))      --> true 30 10
或者把resume的參數,會被傳遞給yield。
hxc = coroutine.create (function ()
    print("hxc", coroutine.yield())
end)
coroutine.resume(hxc)
coroutine.resume(hxc, 4, 5)                   --> hxc 4 5
3) yield也會把多餘的參數返回給對應的resume,以下:

co = coroutine.create(function()
    print("co", coroutine.yield())
  end)多線程

coroutine.resume(co)
coroutine.resume(co , "oo" , "xx")異步

輸出結果是:co oo xx ()      --[爲什麼第一個resume沒有任何輸出呢? 答案是,yield沒有返回,print就根本還沒運行。]函數

4)當一個coroutine結束的時候,main函數的全部返回值都被返回給resume:oop

co = coroutine.create(function()
    return "ok", "no"
  end)
print(coroutine.resume(co))lua

輸出結果是:true ok nospa

  協程的用途最明顯的地方是須要訪問某個異步的功能時,C語言常採用回調的方法:當異步完成時,回調腳本的一個已知的函數。若是程序執行到異步點時,跳回,當異步完成後,再回到跳回點繼續執行。個人理解就是協程是把異步過程,看成同步處理( 所以 也可將一些耗時的操做放置在coroutine中進行,也不至於耽擱其餘邏輯的運行)。
 
  摘取一段 雲風的代碼來詳盡解釋協程的工做機制,在這段代碼中,展現了main thread和協程co之間的交互:
function foo(a)
    print("foo", a)
    -- a[1] = 3
    return coroutine.yield(2 * a)
end

co = coroutine.create(function ( a, b )
    print("co-body_01", a, b)
    local r = foo(a + 1)
    print("co-body_02", r)
    local r, s = coroutine.yield(a + b, a - b)
    print("co-body_03", r, s)
    return b, "end"
end)

print("---main---", coroutine.resume(co, 1, 10))
print("---main---", coroutine.resume(co, "r7"))
print("---main---", coroutine.resume(co, "x", "y"))
print("---main---", coroutine.resume(co, "x", "y"))

運行結果以下:操作系統

co-body_01    1    10
foo    2
---main---    true    4
co-body_02    r7
---main---    true    11    -9
co-body_03    x    y
---main---    true    10    end
---main---    false    cannot resume dead coroutine

假若將「-- a[1] = 3」  這一行註釋打開,運行則是這樣的:.net

co-body_01 1 10
foo    2
main    false    D:\UserProfiles\Jeff\Desktop\t_corotine.lua:13: attempt to index local 'a' (a number value)
main    false    cannot resume dead coroutine
main    false    cannot resume dead coroutine
main    false    cannot resume dead coroutine

[Finished in 0.1s]

corotine如此這般做用,也使得有些童鞋能夠將該功用 看成Xpcall抑或是pcall使用;將易出錯的代碼寫在協程內,即使出錯也不會是的程序崩潰;

(二)coroutine的和callback的比較

  coroutine常常被用來和callback進行比較,由於一般來講,coroutine和callback能夠實現相同的功能,即異步通訊,好比說下面的這個例子:

bob.walkto(jane)
bob.say("hello")
jane.say("hello")

看起來好像是對的,但實際上因爲這幾個動做walkto,say都是須要必定時間才能作完的,因此這段程序若是這樣寫的話,就會致使bob一邊走一邊對jane說hello,而後在同時jane也對bob說hello,致使整個流程十分混亂。

若是使用回調來實現的話,代碼示例以下:

bob.walto(function (  )
    bob.say(function (  )
        jane.say("hello")
    end,"hello")
end, jane)

即walto函數回調say函數,say函數再回調下一個say函數,這樣回調看起來十分混亂,讓人沒法一下看出這段代碼的意義.

若是用coroutine的話,可使用以下寫法:

co = coroutine.create(function (  )
    local current = coroutine.running
    bob.walto(function (  )
        coroutine.resume(current)
    end, jane)
    coroutine.yield()
    bob.say(function (  )
        coroutine.resume(current)
    end, "hello")
    coroutine.yield()
    jane.say("hello"end)

coroutine.resume(co)

在上述代碼中,一旦一個異步函數被調用,協程就會使用coroutine.yield()方法將該協程暫時懸掛起來,在相應的回調函數中加上coroutine.resume(current),使其返回目前正在執行的協程中。

可是,上述代碼中有許多重複的地方,因此能夠經過將封裝的方式將重複代碼封裝起來:

function runAsyncFunc( func, ... )
    local current = coroutine.running
    func(function (  )
        coroutine.resume(current)
    end, ...)
    coroutine.yield()
end

co = coroutine.create(function (  )
    runAsyncFunc(bob.walkto, jane)
    runAsyncFunc(bob.say, "hello")
    jane.say("hello")
end)

coroutine.resume(co)
coroutine.resume(co)
coroutine.resume(co)

這樣就不須要改變從前的全部回調函數,便可經過攜程的方式解決異步調用的問題,使得代碼的結構很是清晰。

(三)用coroutine實現迭代器

  能夠把迭代器 循環當作是一個特殊的producer-consumer例子:迭代器produce,循環體consume。下面咱們就看一下coroutine爲咱們提供的強大的功能,用coroutine來實現迭代器。

咱們來遍歷一個數組的全排列。先看一下普通的loop實現,代碼以下:

function printResult(a)  
    for i = 1, #a do  
        io.write(a[i], ' ')  
    end   
    io.write('\n')  
end  
   
function permgen(a, n)                                                                                                               
    n = n or #a  
    if n <= 1 then  
        printResult(a)  
    else  
        for i = 1, n do  
            a[n], a[i] = a[i], a[n]  
            permgen(a, n-1)  
            a[n], a[i] = a[i], a[n]  
        end   
    end   
end  
   
permgen({1,2,3})

再看一下迭代器實現,注意比較下代碼的改變的部分:

function printResult(a)
    for i = 1, #a do
        io.write(a[i], ' ')
    end 
    io.write('\n')
end  
        
function permgen(a, n)
    n = n or #a
    if n <= 1 then
       coroutine.yield(a) 
    else
        for i = 1, n do
            a[n], a[i] = a[i], a[n]
            permgen(a, n-1)
            a[n], a[i] = a[i], a[n]
        end 
    end 
end  
        
function permutations(a)
    local co = coroutine.create(function () permgen(a) end)                                                                        
    return function ()
        local code, res = coroutine.resume(co)
        return res 
    end 
end  
        
for p in permutations({"a", "b", "c"}) do
    printResult(p)
end 

permutations 函數使用了一個Lua中的常規模式,將在函數中去resume一個對應的coroutine進行封裝。Lua對這種模式提供了一個函數coroutine.wap 。跟create 同樣,wrap 建立一個新的coroutine ,可是並不返回給coroutine,而是返回一個函數,調用這個函數,對應的coroutine就被喚醒去運行。跟原來的resume 不一樣的是,該函數不會返回errcode做爲第一個返回值,一旦有error發生,就退出了(相似C語言的assert)。使用wrap, permutations能夠以下實現:

function permutations (a)
    return coroutine.wrap(function () permgen(a) end)
end

wrap 比create 跟簡單,它實在的返回了咱們最須要的東西:一個能夠喚醒對應coroutine的函數。 可是不夠靈活。沒有辦法去檢查wrap 建立的coroutine的status, 也不能檢查runtime-error(沒有返回errcode,而是直接assert)。

參考文章: Here  and  Here

相關文章
相關標籤/搜索