1. 程式人生 > >lua學習之協同程序

lua學習之協同程序

coroutine 消費 堆棧 end com 活性 人的 應該 我們

轉載:http://www.cnblogs.com/sifenkesi/p/3824321.html  

  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的值傳遞上。

另一篇講得比較清楚的博文: http://blog.chinaunix.net/uid-28295089-id-4150141.html

lua學習之協同程序