Lua 协程 (coroutine)
协程(coroutine) 与线程类似:
拥有独立的堆栈,独立的局部变量,独立的指令指针 同时又与其它协同程序共享全局变量和其它大部分东西
协程是非常强大的功能,但是用起来也很复杂
线程和协同程序区别
线程与协同程序的主要区别在于
一个具有多个线程的程序可以同时运行几个线程 而协同程序却需要彼此协作的运行
协程的特点在于
在任一指定时刻只有一个协同程序在运行,并且这个正在运行的协程只有在明确的被要求挂起的时候才会被挂起
下列是 Lua 中 协程(corouting) 的常用函数
方法 | 描述 |
---|---|
coroutine.create() | 创建 coroutine,返回 coroutine, 参数是一个函数 |
coroutine.resume() | 重启 coroutine |
coroutine.yield() | 挂起 coroutine,将 coroutine 设置为挂起状态 |
coroutine.status() | 查看 coroutine 的状态 coroutine的状态有三种:dead,suspend,running |
coroutine.wrap() | 创建 coroutine,返回一个函数 调用 wrap 函数,就进入 coroutine,和 create 功能一样 |
coroutine.running() | 返回正在运行的 coroutine,一个 coroutine 就是一个线程 当使用 running 的时候,就是返回一个 corouting 的线程号 |
范例
下面的范例演示了 协程(corouting) 各个方法的使用:
-- !/usr/bin/lua -- -*- encoding:utf-8 -*- -- filename: main.lua -- author: 简单教程(www.twle.cn) -- Copyright © 2015-2065 www.twle.cn. All rights reserved. local co = coroutine.create( function(i) print(i); end ) coroutine.resume(co, 1) -- 1 print(coroutine.status(co)) -- dead print("----create & resume end------") co = coroutine.wrap( function(i) print(i*2); end ) co(1) print("----- wrap end -------") co2 = coroutine.create( function() for i=1,10 do print(i) if i == 3 then print(coroutine.status(co2)) --running print(coroutine.running()) --thread:XXXXXX end coroutine.yield() end end ) coroutine.resume(co2) --1 coroutine.resume(co2) --2 coroutine.resume(co2) --3 print(coroutine.status(co2)) -- suspended print(coroutine.running()) print("----------")
运行以上 Lua 脚本,输出结果如下:
$ lua main.lua 1 dead ----create & resume end------ 2 ----- wrap end ------- 1 2 3 running thread: 0x7fbfc2c07758 false suspended thread: 0x7fbfc3001008 true ----------
从 coroutine.running 可以看出:协程(coroutine) 的底层实现就是一个线程
调用 coroutine.create 函数生成一个 coroutine 其实就是向新线程注册了一个事件
当使用 coroutine.resume 函数触发事件的时候,create 的 coroutine 函数就被执行了 当 coroutine 遇到 yield 的时候就代表挂起当前线程,等候再次 resume 触发事件
范例 2 : coroutine 的使用
-- !/usr/bin/lua -- -*- encoding:utf-8 -*- -- filename: main.lua -- author: 简单教程(www.twle.cn) -- Copyright © 2015-2065 www.twle.cn. All rights reserved. function mul2 ( i ) print("mul2 函数输出:", i) return coroutine.yield( 2 * i) -- 返回 2*i 的值 end local co = coroutine.create(function (a , b) print("第一次协同程序执行输出", a, b) -- co-body 1 10 local r = mul2(a + 1) print("第二次协同程序执行输出", r) local r, s = coroutine.yield(a + b, a - b) -- a,b的值为第一次调用协同程序时传入 print("第三次协同程序执行输出", r, s) return b, "结束协同程序" -- b的值为第二次调用协同程序时传入 end) print("main", coroutine.resume(co, 1, 10)) -- true, 4 print("\n--分割线----") print("main", coroutine.resume(co, "r")) -- true 11 -9 print("\n---分割线---") print("main", coroutine.resume(co, "x", "y")) -- true 10 end print("\n---分割线---") print("main", coroutine.resume(co, "x", "y")) -- cannot resume dead coroutine print("\n---分割线---")
运行以上 Lua 脚本,输出结果如下:
$ lua main.lua 第一次协同程序执行输出 1 10 mul2 函数输出: 2 main true 4 --分割线---- 第二次协同程序执行输出 r main true 11 -9 ---分割线--- 第三次协同程序执行输出 x y main true 10 结束协同程序 ---分割线--- main false cannot resume dead coroutine ---分割线---
从范例运行的结果可知:
-
调用 resume,将协同程序唤醒,resume 操作成功返回 true,否则返回 false
-
协程运行
-
运行到 yield 语句;
-
yield 挂起协程,第一次 resume 返回;(注意:此处 yield 返回,参数是 resume 的参数)
-
第二次 resume,再次唤醒协同程序;(注意:此处 resume 的参数中,除了第一个参数,剩下的参数将作为 yield 的参数)
-
yield返回
-
协同程序继续运行
-
如果使用的协同程序继续运行完成后继续调用 resume方法则输出:cannot resume dead coroutine
resume 和 yield 的配合强大之处在于:
resume 处于主程中,它将外部状态(数据)传入到协同程序内部 而 yield 则将内部的状态(数据)返回到主程中
生产者-消费者问题
Lua 中的 协程( coroutine ) 是用来解决 生产者 - 消费者经典问题最好的解决方案
-- !/usr/bin/lua -- -*- encoding:utf-8 -*- -- filename: main.lua -- author: 简单教程(www.twle.cn) -- Copyright © 2015-2065 www.twle.cn. All rights reserved. local _new_productor function productor() local i = 0 while true do i = i + 2 send(i) -- 将生产的物品发送给消费者 end end function consumer() while true do local i = receive() -- 从生产者那里得到物品 print(i) end end function receive() local status, value = coroutine.resume( _new_productor ) return value end function send(x) coroutine.yield(x) -- x 表示需要发送的值,值返回以后,就挂起该协程 end -- 启动程序 _new_productor = coroutine.create(productor) consumer()
运行以上 Lua 脚本,输出结果如下
2 4 6 8 10 12 14 ...