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