深入理解lua的协程coroutine
1. 概述
lua協程和多線程
相同之處:擁有自己獨立的桟、局部變量和PC計數器,同時又與其他協程共享全局變量和其他大部分東西
不同之處:一個多線程程序可以同時運行幾個線程(并發執行、搶占),而協程卻需要彼此協作地運行,并非真正的多線程,即一個多協程程序在同一時間只能運行一個協程,并且正在執行的協程只會在其顯式地要求掛起(suspend)時,它的執行才會暫停(無搶占、無并發)。注意:由于Lua中的協程無法在外部將其停止,而且有可能導致程序阻塞
2. 函數介紹
Lua中所有與協程相關的函數都在coroutine(一個table)中,其中主要的函數如下
其他函數:
2.1 coroutine.isyieldable()? ? :????如果正在運行的協程可以讓出,則返回true。值得注意的是,只有主協程(線程)和C函數中是無法讓出的
2.2?coroutine.wrap()????????? ?:????wrap()也是用來創建協程的
只不過這個協程的句柄是隱藏的。跟create()的區別在于:
(1)、wrap()返回的是一個函數,每次調用這個函數相當于調用coroutine.resume()。
(2)、調用這個函數相當于在執行resume()函數。
?
?
(3)、調用這個函數時傳入的參數,就相當于在調用resume時傳入的除協程的句柄外的其他參數。
?
?
(4)、調用這個函數時,跟resume不同的是,它并不是在保護模式下執行的,若執行崩潰會直接向外拋出
? ? wrap()函數的示例代碼:
co = coroutine.wrap(????function (a,b)print("resume args:"..a..","..b)yreturn = coroutine.yield()print ("yreturn :"..yreturn)end) print(type(co)) co(11,22) co(33)????結果如下:
function resume args:11,22 yreturn :33特別注意:
1. coroutine.resume()函數
用來首次啟動或再次啟動一個協程,使其由掛起狀態變成運行狀態。也可以這么說,resume函數相當于在執行協程中的方法。參數Val1...是執行協程co時傳遞給協程的參數。
(1)?首次調用resume執行協程co時,參數Val1...會賦值給協程co的函數,作為函數參數
(2) 以后再調用resume執行協程co時,參數Val1...會賦值給協程co中上一次yield的返回值
resume函數的返回有3種情況:
(1)?如果協程co的函數執行完畢,協程正常終止,resume 返回 true和函數的返回值。
(2)?如果協程co的函數執行過程中,協程讓出了(調用了yield()方法),那么resume返回true和協程中調用yield傳入的參數。
(3)?如果協程co的函數執行過程中發生錯誤,resume返回false與錯誤消息。
可以看到resume無論如何都不會導致程序崩潰。它是在保護模式下執行的
2. coroutine.yield()函數
使正在執行的協程掛起,注意是執行該函數中會使協程掛起,該函數并未執行結束,下次resume()時才會執行完畢
(1)?yeild的參數會作為resume的第二個返回值
(2)?如果對該協程不是第一次執行resume,resume函數傳入的參數將會作為yield的返回值
yield()和resume()的關系如下圖
?
3. 協程狀態
suspended:掛起狀態,協程剛創建完成時或者yield之后
running????? ?:運行狀態,如果在協程的函數中調用status,傳入協程自身的句柄,那么執行到這里的時候才會返回running狀態
normal????? ? :如果協程A ?resume() 協程B時,則協程A處于的狀態為normal。在協程B的執行過程中,協程A就一直處于normal狀態。因為它這時候既不是掛起狀態、也不是運行狀態
dead??????????:結束狀態,如果一個協程發生錯誤結束或正常運行結束。那么就處于dead狀態,這時候如果調用resume()的話會直接返回false,且報錯"cannot resume dead coroutine"
4. 代碼示例
4. 1 ?協程狀態及yield()與resume()的交互
-- 打印協程1和協程2的狀態 function status()print("co1's status :"..coroutine.status(co1).." ,co2's status: "..coroutine.status(co2)) end-- 協程1 co1 = coroutine.create(function ( a )print("co1 arg is :"..a)status()-- 喚醒協程2local stat,rere = coroutine.resume(co2,"2")print("111 co2 resume's return is "..rere)status()-- 再次喚醒協程2local stat2,rere2 = coroutine.resume(co2,"4")print("222 co2 resume's return is "..rere2)local arg = coroutine.yield("6") end)-- 協程2 co2 = coroutine.create(function ( a )print("co2 arg is :"..a)status()local rey = coroutine.yield("3")print("co2 yeild's return is " .. rey)status()coroutine.yield("5") end)--主線程執行協程co1,傳入字符串“main thread arg” stat,mainre = coroutine.resume(co1,"main thread arg") status() print("last return is "..mainre)結果及筆者的注釋:
co1 arg is :main thread arg -- 開始執行協程1,第8行 co1's status :running ,co2's status: suspended -- 協程1中,第9行,調用了status()函數 co2 arg is :2 -- 協程1中,第12行,調用了resume(),喚醒協程2,調用到24行 co1's status :normal ,co2's status: running -- 注意:此時協程1處于normal狀態,協程2處于running狀態 111 co2 resume's return is 3 -- 由于26行,協程2執行了yiled(),協程掛起,參數“3”被返回到協程1,賦值給了12行中resume()的第二個參數,在13行進行此打印 co1's status :running ,co2's status: suspended -- 此時協程1被喚醒,處于running狀態,協程2處于掛起狀態 co2 yeild's return is 4 -- 由于17行,協程2被再次喚醒,由于不是第一次調用resume(),參數“4”被賦值給上次26行的yiled()的返回值,打印出來,此時是27行的 co1's status :normal ,co2's status: running -- 同第一次,此時協程1處于normal狀態,協程2處于running狀態 222 co2 resume's return is 5 -- 由于第29行執行yield完畢,參數5作為17行的resume()的返回值,在18行進行了打印,注意此時協程2仍未結束,處于掛起狀態 co1's status :suspended ,co2's status: suspended -- 由于第19行,執行了yield(),參數“6”被返回給33行的mainre,注意:此時協程1掛起,同樣也未執行完 last return is 6 -- 最終35行進行了打印,mainre的值,也就是resume()的第二個返回值其實就是yidld()的參數4.2 下面這段代碼摘取云風的,演示yield()和resume()的交互
function foo(a)print("foo", a)return coroutine.yield(2 * a) endco = coroutine.create(function ( a, b )print("co-body", a, b)local r = foo(a + 1)print("co-body", r)local r, s = coroutine.yield(a + b, a - b)print("co-body", r, s)return b, "end" end)print("main", coroutine.resume(co, 1, 10)) print("main", coroutine.resume(co, "r")) print("main", coroutine.resume(co, "x", "y")) print("main", coroutine.resume(co, "x", "y"))結果及筆者的備注:
co-body 1 10 -- 協程co的第7行,此時resume()傳入的參數是賦值給了函數的 foo 2 -- 在第8行里面調用了函數foo(),執行到第2行的打印 main true 4 -- 由于函數foo()的第3行yield()執行后掛起,參數是4,作為第15行的resume()的第二個返回值,最終打印了出來,到此,第15行執行完畢 co-body r -- 第16行resume()再次喚醒協程co,接著上次yield()的地方繼續執行,參數“r"被賦值給上次yield()的返回值,在第9行打印出來 main true 11 -9 -- 在第10行yiled()后再次掛起協程co,并返回,此時參數a和b還是第一次resume()時的參數,1,10,所以yield()兩個參數分別為11,-9,作為resum()的第二個返回值,最終被打印出來,到此,第16行執行完畢 co-body x y -- 第17行resume()再次喚醒協程co,傳入的參數“x”,“y”被賦值給上次的yield()函數的返回值,即賦值給第10行的r,s,在第11行被打印出來 main true 10 end -- 協程co在第12行返回,注意此時參數b仍然是第一次resume()時的參數2,值為10,至此協程co執行結束,變為dead狀態,最終在第17行打印出來 main false cannot resume dead coroutine -- 第18行嘗試再次resume()協程co,由于協程co已經為dead狀態,所以直接返回并報錯4.3 生產者消費者
-- 生產者協程,負責產生數據(由控制臺輸入),然后掛起協程,把值傳遞給過濾器協程 produceFunc = function()while true dolocal value = io.read() -- 等待輸入,即生產數據print("produce: ", value)coroutine.yield(value) -- 掛起本生產者協程,返回生產的值end end-- 過濾器協程,喚醒生產者協程,等待其產生數據,得到數據后,負責把數據放大100倍,然后掛起協程,把值傳遞給消費者函數 filteFunc = function(p)while true dolocal status, value = coroutine.resume(p); -- 喚醒生產者協程,直到其返回數據value = value *100 -- 把數據放大100倍print("filte: ", value)coroutine.yield(value) -- 掛起本過濾器協程,返回處理后的值end end-- 消費者,只是個函數,并非協程,while一直調用,即一直喚醒過濾器協程 consumer = function(f, p)while true dolocal status, value = coroutine.resume(f, p);--喚醒過濾器協程,參數是生產者協程print("consume: ", value) -- 打印出得到的值,即消費end end--備注: -- 1. 消費者驅動的設計,也就是消費者需要產品時找生產者請求,生產者完成生產后提供給消費者 -- 2. 這里做了中間的過濾器協程,即消費者函數找過濾器協程,過濾器協程找生產者協程,等待其返回數據,再原路返回,傳遞給消費者函數,while一直循環-- 生產者協程 producer = coroutine.create(produceFunc)--過濾器協程 filter = coroutine.create(filteFunc)-- 消費者函數,傳入過濾器協程和生產者協程 consumer(filter, producer)結果:筆者輸入123,99做示例
123 produce: 123 filte: 12300 consume: 12300 99 produce: 99 filte: 9900 consume: 9900?
?
?
?
總結
以上是生活随笔為你收集整理的深入理解lua的协程coroutine的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: lua的元表metatable及元方法
- 下一篇: Lua基础之math(数学函数库)