1、什么是协同程序
协同程序(coroutine)与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,但是和其他协同程序共享全局变量等很多信息。
线程和协同程序的主要不同在于:在多处理器情况下,从概念上来讲多线程程序同时运行多个线程;而协同程序是通过协作来完成,在任一指定时刻只有一个协同程序在运行,并且这个正在运行的协同程序只有在明确的被要求挂起的时候才会被挂起。协作程序有点类似同步的多线程,在等待同一个线程锁的几个线程有点类似协同。
协同和顺序执行的区别?任一制定时刻只能运行一个协同程序,这个和顺序执行区别在哪?关键在于yield函数。如果顺序执行的时候进行耗费cpu时间或者一直等待某个资源的时候,程序将卡在这个地方不能前进。协同程序的出现就是可以使等待资源的线程让出资源,进行下一个协同程序的操作。yield可以在执行出错的时候挂起,下次恢复的时候再进行操作
2、 协同(coroutine)的几个状态。挂起、running(运行)、dead(函数走完后的状态,这时候不能再重新resume)。用函数status 来查看该coroutine 的状态。
挂起态:协同刚创建完成时或者调用coroutine.yield之后的状态。
运行态:函数coroutine.resume可以使程序由挂起状态变为运行态
停止态:协同程序结束,进入停止态
正常态:处于挂起和运行之间的状态。在后续进行补充说明。
例子1:
co = coroutine.create(function() print('hi') end)
print(co,type(co))
print(coroutine.status(co))--检查协同的状态
coroutine.resume(co)--使程序由挂起状态变为运行态
print(coroutine.status(co))--打印hi之后便进入dead状态
coroutine看起来好像也就这么回事,类似函数调用,只是更复杂的函数调用。但是,coroutine的真正强大之处在于它的 yield 函数,它可以将正在运行的coroutine 挂起,并可以在适当的时候再重新被唤醒,然后继续运行。
例子2:使用yield函数进行运行代码的挂起
co = coroutine.create(function()
for i=1,4 do
print("co",i)
coroutine.yield()--运行代码挂起
end
end)
coroutine.resume(co)--》co 1
print(coroutine.status(co))-->suspended
--使用yield函数将程序挂起,当激活被挂起的程序时,yield返回并继续程序的运行直到再次遇到yield或者程序结束
coroutine.resume(co)-->co 2,激活程序,继续运行
coroutine.resume(co)-->co 3,继续运行。因为在程序设计中我们可以看出,每个循环中都将程序进行挂起。
coroutine.resume(co)
coroutine.resume(co)--print nothing。这是因为在最后一次调用时,协同体已经结束,此时的协同程序处于终止状态。如果此时,仍旧试图激活ta,则resume将返回false和错误信息
print(coroutine.resume(co))
该coroutine每打印一行,在运行 yield 函数的时候被挂起。当我们用 resume 唤醒该coroutine时,该coroutine继续运行,打印出下一行。直到最后没有东西打印出来的时候,该coroutine退出循环,变为dead状态(注意最后那里的状态变化)。如果对一个dead状态的coroutine进行 resume 操作,那么resume会返回false+err_msg,
注意:resume运行在保护模式下,因此如果协同内部存在错误Lua并不会抛出错误而是将错误返回给resume函数。所以,通过打印resume的返回信息才能看到具体的错误。
当一个coroutine A在resume另一个coroutine B时,A的状态没有变为suspended,我们不能去resume它;但是它也不是running状态,因为当前正在running的是B。这时A的状态其实就是normal 状态了。
3、Lua中一对resume-yield可以相互交换数据。
1)main函数中没有 yield ,调用 resume 时,多余的参数,都被传递给coroutine的main函数作为参数,下面的示例,1 2 3分别就是a b c的值了:
co = coroutine.create(function(a,b,c)
print("co",a,b,c+2)
--coroutine.yield(a+b,a-b)
end)
coroutine.resume(co,1,2,3)--co 1 2 5
print(coroutine.resume(co,1,2,3))--false cannot resume dead coroutine
2)main函数中有 yield ,所有被传递给yield的参数,都被返回。因此resume的返回值,除了标志正确运行的 true 外,还有传递给yield的参数值:
co = coroutine.create(function(a,b,c)
coroutine.yield(a+b,a-b)
end)
--coroutine.resume(co,1,2,3)
print(coroutine.resume(co,1,2,3))-->true 3 -1
如果先调用一次呢?
co = coroutine.create(function(a,b,c)
coroutine.yield(a+b,a-b)
end)
coroutine.resume(co,1,2,3)--是没有打印返回结果的,但是此时的返回结果是上述的true 3 -1
print(coroutine.resume(co,1,2,3))-->true,此时的返回结果只有一个true
3)yield也会把多余的参数返回给对应的resume,如下
co = coroutine.create(function(a,b)
print("co1",a)
print("co2",coroutine.yield())
print("co3",coroutine.yield())
print("co4",coroutine.yield())
end)
coroutine.resume(co,'hello',2,3,4)
coroutine.resume(co,'hello',2,3,4)--第一个resume没有print co2是因为yield没有返回,对应的print("co2",coroutine.yield())没有运行
coroutine.resume(co,1,2,3,4,5)--yield把多余的参数返回给resume
coroutine.resume(co,10.,11,12)
运行结果如下所示:
注意其中的对应关系。如果把print("co1",a)修改为print("co1",coroutine.yield())呢?
代码如下:
co = coroutine.create(function(a,b)
print("co1",coroutine.yield())
print("co2",coroutine.yield())
print("co3",coroutine.yield())
print("co4",coroutine.yield())
end)
coroutine.resume(co,'he',2,3,4)
coroutine.resume(co,'hello',5,6,7)--
coroutine.resume(co,1,2,3,4,5)--yield把多余的参数返回给resume
coroutine.resume(co,10.,11,12)
运行结果如下:
从上述运行结果可以看出,resume调用了四次,但是返回结果只有3次的。第一次调用的coroutine.resume(co,'he',2,3,4)并没有返回。
4)当一个coroutine结束的时候,main函数的所有返回值都被返回给resume:
co = coroutine.create(function()
return 6,7
end)
print(coroutine.resume(co))-->true 6 7
Lua提供的是coroutine是非对称的,即所谓的asymmetric coroutine,意思是说,它需要一个函数(yield)来挂起一个coroutine,但需要另一个函数(resume)来唤醒这个被挂起的coroutine。对应的,一些语言提供了symmetric coroutine,用来切换当前coroutine的函数只有一个。