深入理解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()

	-- 唤醒协程2
	local stat,rere = coroutine.resume(co2,"2")
    print("111 co2 resume's return is "..rere)
    status()

	-- 再次唤醒协程2
	local 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)
end

co = 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 do
        local value = io.read()						-- 等待输入,即生产数据
        print("produce: ", value)
        coroutine.yield(value)   					-- 挂起本生产者协程,返回生产的值
    end
end

-- 过滤器协程,唤醒生产者协程,等待其产生数据,得到数据后,负责把数据放大100倍,然后挂起协程,把值传递给消费者函数
filteFunc = function(p)
    while true do
        local status, value = coroutine.resume(p); 	-- 唤醒生产者协程,直到其返回数据
        value = value *100        					-- 把数据放大100倍
		print("filte: 	", value)
        coroutine.yield(value)						-- 挂起本过滤器协程,返回处理后的值
    end
end

-- 消费者,只是个函数,并非协程,while一直调用,即一直唤醒过滤器协程
consumer = function(f, p)
    while true do
        local 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

 

 

 

 

  • 13
    点赞
  • 64
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值