Lua 协同程序(coroutine)与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。
协程有三种状态:挂起,运行,停止。创建后是挂起状态,即不自动运行。status函数可以查看当前状态。协程可通过yield函数将一段正在运行的代码挂起。
lua的resume-yield可以互相交换数据。如果没有对应的yield,传递给resume的额外参数将作为参数传递给协程主函数:
co = coroutine.create(function (a, b, c)
print("co", a, b, c)
end)
coroutine.resume(co, 1, 2, 3)
运行结果: co 1 2 3
如果没有错误的话,resume将返回true和yield的参数,弱出现错误,返回false与相关错误信息:
co = coroutine.create(function (a, b)
coroutine.yield(a+b, a-b)
end)
print(coroutine.resume(co, 3, 8))
运行结果: true 11 5
同样地,yield也将返回由对应的resume传递而来的参数:
co = coroutine.create (function ()
print("begin coroutine")
print("co", coroutine.yield())
end)
coroutine.resume(co) -- 遇到coroutine.yield()挂起
coroutine.resume(co, 4, 5) -- 完成第二个print过程
运行结果: begin coroutine
co 4 5
协程主函数返回值将作为与之对应的resume的返回值(第一个参数是true):
co = coroutine.create(function (a)
return 6, 7, coroutine.yield(a-1)
end)
print(coroutine.resume(co,5)) -- 输出true与coroutine.yield()运行参数
print(coroutine.resume(co,5)) -- 输出协程主函数返回值
运行结果: true 4
true 6 7 5
下面用协同程序实现一个典型的生产者-消费者模式。
function receive(prod)
local status,value = coroutine.resume(prod)
return value
end
function send( x )
coroutine.yield(x) -- 挂起,返回当前协同程序 供resume调用
end
function produce() -- 生产者
return coroutine.create(function ()
while true do -- 该循环保证了生产者被无限驱动
local x = io.read()
send(x)
end
end)
end
function consume(prod) -- 消费者
while true do
local x = receive(prod) -- 消费者驱动生产者的执行
print(x)
end
end
function filter( prod ) -- 用过滤器处理生产者数据
return coroutine.create(function ()
while true do
local x = receive(prod) -- 驱动生产者生成数据
x = doSomeProcess(x)
send(x)
end
end)
end
consume(filter(produce()))
我们尝试用协程来实现一些算法和应用级的效果。
1)用生产者-消费者模式实现迭代器,该迭代器能输出一个数组的全排列。
function permgen( a,n ) -- 生产者
if n <= 0 then
coroutine.yield(a)
else
for i=1,n do
a[i],a[n] = a[n],a[i]
permgen(a,n-1)
a[i],a[n] = a[n],a[i]
end
end
end
function perm( a ) -- 迭代器 消费者
return coroutine.wrap(function ()
permgen(a,#a)
end)
--[[
-- 与上面代码效果相同
local prod = coroutine.create(function ()
permgen(a,#a)
end)
return function ()
local code,rt = coroutine.resume(prod)
return rt
end
]]
end
local a = {1,2,3}
for v in perm(a) do
local st = ""
for i,v in ipairs(v) do
st = st .. v .. " "
end
print(st)
end
2)用协程实现一个多文件下载分发器
有多个文件需要下载,如果采用顺序排队方式,那么大量时间将浪费在 请求下载-下载完成反馈之间的等待过程中。我们用协程解决这一效率问题:请求下载后,如果超时则yield挂起当前协程,并有分发器调用其他协程。
download = function (host,file)
local c = socket.connect(host,80)
c:send("GET " .. file .. "HTTP/1.0\r\n") -- 发送请求
while true do
local s,status = receive(c) -- 接收
if status == "closed" then
-- 当前协程的下载过程已完成
break
end
end
end
receive = function (connection)
local s,status = connection:receive()
if status == "timeout" then
coroutine.yield(connection)
end
return s,status
end
dispatch = function ()
while true do
if #loadLst <= 0 then
-- 所有下载器均完成 结束分发
break
end
for i,v in ipairs(loadLst) do
local status,res = coroutine.resume(v)
if not res then
table.remove(loadLst,i) -- 当前下载器完成
break
end
end
end
end
游戏中,有时在处理消息时,希望一条一条消息独立处理(独立堆栈),且希望每条消息在不同场景内等待式逐步进行(如一个场景消息处理完,挂起,经过100ms再进行当前消息下一场景的处理),协程能够完成这一过程。下面提供一种实现方案。
local msgLst = {} -- 存储
local curMsgCor = nil -- 当前消息对应的协程
function insertPerMsg( msg )
table.insert(msgLst,msg)
scheduleScript(processMsg) -- -- 定时器中循环执行函数
end
function processMsg( )
if #msgLst <= 0 then
unScheduleScript(processPerMsgCor)
else
if not curMsgCor then
local curMsg = table.remove(msgLst,1)
curMsgCor = coroutine.create(function () processPerMsgCor(curMsg) end) -- 创建coroutine
end
if curMsgCor then
local state,errMsg = coroutine.resume(curMsgCor) -- 重启coroutine
local status = coroutine.status(curMsgCor) -- 查看coroutine状态: dead,suspend,running
-- 启动失败
if not state then
curMsgCor = nil
local debugInfo = debug.traceback(curMsgCor)
print(debugInfo)
end
-- coroutine消亡
if status == "dead" then
curMsgCor = nil
end
end
end
end
function processPerMsgCor( curMsg )
processForTalk()
coroutine.yield() -- 挂起coroutine
processForSpecialRoom()
coroutine.yield()
processForOtherWay()
end
local Event = class("Event")
Event.State = {
None = 1,
Doing = 2,
Done = 3,
}
function Event:ctor()
self.state = self.State.None
end
function Event:isNotStart()
return self.state == self.State.None
end
function Event:setDoing()
self.state = self.State.Doing
end
function Event:setFunc( func,... )
self.func = func
self.funcParams = {...}
end
function Event:doFunc()
self.func(self,unpack(self.funcParams))
end
-- Func完成后调用
function Event:setDone()
self.state = self.State.Done
end
function Event:isDone()
return self.state == self.State.Done
end
function Test:ctor( )
-- 创建事件队列
self.eventQueue = Queue:create()
-- 定时器轮询时间事件队列
local schedule = cc.Director:getInstance():getScheduler()
self.scheduleId = schedule:scheduleScriptFunc(handler(self,self.runEventQueue),1,false)
-- 添加几个事件
local deltaTime = cc.DelayTime:create(5)
local event = Event:create()
-- func中的参数event在执行doFunc时传入
local func = function (event)
local f1 = function()
-- 5秒后输出
print("print this after 5 sec")
event:setDone()
end
self:runAction(cc.Sequence:create(deltaTime,cc.CallFunc:create(f1)))
end
event:setFunc(func)
self:addEvent(event)
event = Event:create()
func = function (event)
local f1 = function()
-- 10秒后输出
print("print this after 10 sec")
event:setDone()
end
self:runAction(cc.Sequence:create(deltaTime,cc.CallFunc:create(f1)))
end
event:setFunc(func)
self:addEvent(event)
event = Event:create()
func = function (event)
local f1 = function()
-- 15秒后输出
print("print this after 15 sec")
event:setDone()
end
self:runAction(cc.Sequence:create(deltaTime,cc.CallFunc:create(f1)))
end
event:setFunc(func)
self:addEvent(event)
end
function Test:addEvent(event)
self.eventQueue:push_back(event)
end
function Test:runEventQueue()
if self.eventQueue:size() <= 0 then
return
end
-- 两种写法
--[[ 第一种写法:完成当前事件后等待计时器执行下一个事件,事件之间存在时间间隙
local curEvent = self.eventQueue:front()
if curEvent and not curEvent:isDoing() then
if curEvent:isNotStart() then
-- 执行事件
curEvent:setDoing()
curEvent:doFunc()
elseif curEvent:isDone() then
-- 事件已完成,删除并转至下个事件
self.eventQueue:pop_front()
curEvent = nil
if self.eventQueue:size() > 0 then
curEvent = self.eventQueue:front()
end
end
end
]]
-- 第二种写法:当前时间完成即刻执行下一时间
while curEvent and not curEvent:isDoing() do
if curEvent:isNotStart() then
-- 执行事件
curEvent:setDoing()
curEvent:doFunc()
elseif curEvent:isDone() then
-- 事件已完成,删除并转至下个事件
self.eventQueue:pop_front()
curEvent = nil
if self.eventQueue:size() > 0 then
curEvent = self.eventQueue:front()
end
end
end
end