Lua入门教程:协程
从多线程的角度来看,协程(coroutine)与线程(thread)类似:协程时一系列的可执行语句,拥有自己的栈、局部变量和指令指针,同时协程又与其他协程共享全局变量和其他几乎一切资源。
协程与线程的主要区别在于,一个多线程程序可以并行运行多个线程,而协程则需要彼此协作运行,即在任意时刻只能有一个协程运行,且只有当正在运行的协程要求挂起时其执行才会暂停。
协程基础
与协程相关的函数都放在表 coroutine 中。没个新建立的协程都拥有一个完整的生命周期,其包含四种状态:挂起(suspended)、运行(running)、正常(normal)和死亡(dead)。
创建协程
coroutine.create(f),创建一个以执行代码的函数 f(协程体)的新协程,并返回以类型 thread
为标志的新协程。
1 | co = coroutine.create(function() print("hello") end) |
当协程被创建时,默认处于挂起状态,即协程不会在被创建时自动运行,我们可以通过 coroutine.status
来检查协程的状态:
1 | print(coroutine.status(co)) --> suspended |
运行协程
要想启动或再启动一个协程,我们可以使用函数 coroutine.resume
来执行,并将去状态改为运行:
1 | coroutine.resume(co) --> hello |
上面的协程创建后运行的函数会输出 hello
然后该协程就终止了,最后协程执行完成后会变为死亡状态:
1 | print(coroutine.status(co)) --> dead |
挂起协程
协程的强大之处在于函数 coroutine.yield
,该函数可以让一个运行中的协程挂起自己,然后在后续执行中恢复运行:
1 | co = coroutine.create(function() |
上面协程运行着一个循环,在循环中打印输出一个数字后遇到 yield
进入挂起状态:
1 | coroutine.resume(co) --> co 1 |
此时我们可以查看协程状态,会发现协程处于挂起状态:
1 | print(coroutine.status(co)) --> suspended |
我们可以继续运行,直到上面 for 循环执行结束:
1 | coroutine.resume(co) --> co 2 |
当最后一次调用 resume 时,协程体执行完毕并返回,不会输出任何数据,如果我们试图再次唤醒它,函数 resume 会返回 false 已经一条错误信息:
1 | print(coroutine.resume(co)) --> false cannot resume dead coroutine |
请注意,resume 函数运行在保护模式中,如果协程出错,Lua 不会显示错误信息,而是将错误信息返回给 resume 函数。
当协程 A 唤醒协程 B 时,协程 A 不是挂起状态(因为不能唤醒协程A),而不是运行状态(因为正在运行协程B),此时的状态称之为正常状态。
交换数据
协程中一个非常有用的机制是通过一对 resume-yield
来交换数据。第一个 resume
函数会把所有的额外参数传递给协程的主函数:
1 | co = coroutine.create(function(a, b, c) |
函数 resume
第一个返回值为 true
时表示没有错误,之后的返回值对应函数 yield
的参数:
1 | co = coroutine.create(function(a, b) |
函数 yield
返回值则会对应 resume
的参数:
1 | co = coroutine.create(function(x) |
当协程运行结束后,主函数所返回的值将变成对应函数 resume
的返回值:
1 | co = coroutine.create(function() |
使用协程解决生产者于消费者问题
生产者-消费者设计两个函数,一个函数不断的生成值,而另外一个函数则不断的消费这些值,我们可以看以下代码实现:
1 | function () |
上面这种设计中,程序通过消费者启动。当消费者需要新值时就唤醒生成者,生成者向消费者返回新值后挂起,知道消费者再次将其唤醒。因此这种设计称之为消费者驱动式设计。