Lua 程序设计:协程

协程

一个协程(coroutine)与线程(thread,这里指“多线程”中的线程)类似:它是一个执行序列,拥有自己的栈,局部变量,指令指针,但是与其他的协程共享全局变量和其他大部分东西。

从概念上讲,协程与线程最大的区别是:在一个多处理器机器上,一个多线程程序可以并行地执行多个线程。而协程是协作性的,一个程序任意时刻只能执行一个协程,并且只有这个协程在显示地要求挂起时,它的执行才会暂停。

基础

Lua 将所有协程的函数放在一个名为 “coroutine” 的 table 中。其中 create 函数用于创建一个协程,它的参数是一个函数。create 函数会返回一个 thread 类型的值表示协程。

一个协程可以处于4种不同的状态:“挂起”、“运行”、“死亡”和“正常”。使用 status 函数可以查看一个协程的状态。

协程刚创建时处于挂起状态,使用 resume 函数可以启动或再次执行协程,这时它的状态是运行。

当协程执行的函数返回后,协程处于死亡状态。

看下面一个例子:

co = coroutine.create(function () print("hello world") end)
print(co)
print(coroutine.status(co))
coroutine.resume(co)
print(coroutine.status(co))
print(coroutine.resume(co))

输出如下:

thread: 0x1f3c080 
suspended
hello world
dead
false   cannot resume dead coroutine

代码最后一行试图再次执行一个处于死亡状态的协程,这时 resume 函数返回第一个值为 false,表示执行过程出错,第二个值是一个错误描述。

上面的例子仅仅是将协程当作一个普通的函数使用,实际上协程的强大之处在于函数 yield 的使用上。这个函数可以让协程挂起,之后可以使用 resume 函数恢复它的运行。

看个例子:

co = coroutine.create(function()
		for i=1, 5 do
			print("co", i)
			coroutine.yield(i)
		end
		print("ok")
	end)
	
coroutine.resume(co)
coroutine.resume(co)
coroutine.resume(co)
coroutine.resume(co)
coroutine.resume(co)
coroutine.resume(co)

输出如下:

co      1
co      2
co      3
co      4
co      5
ok

程序最后一次调用 resume 函数时,协程函数执行完最后一次 for 循环中的 yield 函数,然后打印出“ok”,函数返回,协程处于死亡状态。

从协程的角度看,所有在它挂起发生的活动都发生在 yield 调用中。当恢复协程的执行时,对于 yield 的调用才最终返回,然后协程执行执行,直到下一个 yield 调用或函数返回。

当一个协程 A 唤醒另一个协程 B 时,A 既不是在挂起状态也不是在运行状态,我们称这个特殊状态为“正常”状态。

协程还有一项有用的机制,就是可以通过一对 resume-yield 调用来交换数据。resume 函数返回的值是执行 yield 函数时实参的值,yield 函数返回的值是执行 resume 函数时传递的参数值。

看如下的例子:

co = coroutine.create(function (a,b)
		print("co1", coroutine.yield(a + b, a - b))
		print("co2", coroutine.yield(a - b, a + b))
	 end)
	 
print("start resume(co, 20, 10)")
print(coroutine.resume(co, 20, 10))
print("start resume(co, 10, 5)")
print(coroutine.resume(co, 10, 5))
print("start resume(co, 20, 10)")
print(coroutine.resume(co, 20, 10))

输出如下:

start resume(co, 20, 10) # 第一个 resume 的参数传递给函数
true	30	10 # 第一个 resume 的返回值是执行第一个 yield 时实参的值:20+10,20-10,协程挂起

start resume(co, 10, 5) # 第二个 resume 的参数传递给第一个 yield ,作为它的返回值,第一个 yield 返回
co1	10	5 # 第一个 yield 返回,协程函数输出
true	10	30 # 第二个 resume 的返回值是执行第二个 yield 时实参的值:20-10,20+10,协程挂起

start resume(co, 20, 10) # 第三个 resume 的参数传递给第二个 yield,作为它的返回值
co2	20	10 # 第二个 yield 返回,协程函数返回
true # 第三个 resume 的返回值是协程函数的返回值

第一次调用 resume 函数时,传递的参数是20和10,由于协程没有运行过,这些参数会作为协程函数的参数,所以协程函数实参 a 的值为20,实参 b 的值为10,当执行第一个 yield 函数时(它的实参的值计算后是30和10),协程挂起(yield 函数并没有返回!)。那么 yield 函数实参的值作为 resume 函数的返回值。我们看到程序输出“true 30 10”。

第二次调用 resume 函数时,传递的参数是10和5,由于协程正处于挂起状态,因此这些参数被传递给第一个 yield 函数作为它的返回值,协程被唤醒后,第一个 yield 函数返回,执行完第一个 print 函数后,程序输出“co1 10 5”,接着执行第二个 yield 函数时,协程被挂起。resume 函数的返回值是执行第二个 yield 函数时实参的值,也就是10和30,程序输出“true 10 30”。

最后一次调用 resume 函数时,传递的参数是20和10,使协程从第二个 yield 函数中返回,之后协程函数返回,由于协程函数没有返回任何值,所以我们只看到程序输出“true”。

简而言之,第一次 resume 使函数执行,然后挂起在第一个 yield。
第二次 resume 使第一个 yield 返回,然后挂起在第二个 yield。
第三次 resume 使第二个 yield 返回,使协程函数返回。

如果一个协程函数有 N 个 yield,那么要调用 N+1 次 resume 使其返回。

生产者和消费者问题

一个关于协程使用的经典示例就是“生产者和消费者”问题。这其中涉及到两个函数,一个函数不断地产生值,一个函数不断地消费值。示例代码如下:

producer = coroutine.create(
	function ()
		while true do
			local x = io.read()
			send(x)
		end
	end
)

function consumer ()
	while true do
		local x = receive()
		io.write(x, "\n")
	end
end

function receive ()
	local status, value = coroutine.resume(producer)
	return value
end

function send (x)
	coroutine.yield(x)
end


consumer()

示例中,生产者函数作为一个协程,主程序直接运行消费者函数。当消费者需要一个值时,它启动或唤醒生产者协程,生产者产生一个值后,挂起自己,将值传递给消费者。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值