烦恼一般都是想太多了。

0%

Lua中的协程

Lua 中没有真正的线程,只有协程,Lua 中的线程概念也是为了协程而产生的。一个协程在 Lua 中代表了一段独立的执行线程。 然而,与多线程系统中的线程的区别在于, 协程仅在显式调用一个让出(yield)函数时才挂起当前的执行。

关键函数

Lua 和协程有关的内容都封装在 coroutine 包中,事实上,也只有几个函数和协程相关。

根据 官方文档

  • coroutine.create (f)创建一个主体函数为 f 的新协程。 f 必须是一个 Lua 的函数。 返回这个新协程,它是一个类型为 “thread” 的对象。
  • coroutine.isyieldable ()如果正在运行的协程可以让出,则返回真。 不在主线程中或不在一个无法让出的 C 函数中时,当前协程是可让出的。
  • coroutine.resume (co [, val1, ···])开始或继续协程 co 的运行。 当你第一次延续一个协程,它会从主体函数处开始运行。 val1, … 这些值会以参数形式传入主体函数。 如果该协程被让出,resume 会重新启动它; val1, … 这些参数会作为让出点的返回值。 如果协程运行起来没有错误, resume 返回 true 加上传给 yield 的所有值 (当协程让出), 或是主体函数的所有返回值(当协程中止)。 如果有任何错误发生, resume 返回 false 加错误消息。
  • coroutine.running ()返回当前正在运行的协程加一个布尔量。 如果当前运行的协程是主线程,其为真。
  • coroutine.status (co)以字符串形式返回协程 co 的状态: 当协程正在运行(它就是调用 status 的那个) ,返回 “running”; 如果协程调用 yield 挂起或是还没有开始运行,返回 “suspended”; 如果协程是活动的,但并不在运行(即它正在延续其它协程),返回 “normal”; 如果协程运行完主体函数或因错误停止,返回 “dead”。
  • coroutine.wrap (f)创建一个主体函数为 f 的新协程。 f 必须是一个 Lua 的函数。 返回一个函数, 每次调用该函数都会延续该协程。 传给这个函数的参数都会作为 resume 的额外参数。 和 resume 返回相同的值, 只是没有第一个布尔量。 如果发生任何错误,抛出这个错误。
  • coroutine.yield (···)挂起正在调用的协程的执行。 传递给 yield 的参数都会转为 resume 的额外返回值。

例子

我们可以来看一个协程的例子:

local co = coroutine.create(function()
for i=1, 2 do
print(i)
coroutine.yield()
end
return "end"
end)

print(type(co))

coroutine.resume(co)
coroutine.resume(co)
coroutine.resume(co)

我们建立一个协程 co , 每次我们运行 co 的时候打印出一个值,就会返回。

例子简单,但是里面有几个细节是值得商讨的。

coroutine.resume() 的返回

当我们对一个协程调用 coroutine.resume() 的时候,这个函数并不会立即返回,因为这个时候,控制权已经交给了我们的协程 co,只有当 co 调用了 coroutine.yield() 函数的时候, coroutine.resume() 才会返回。那么,只有 co 协程中的 print() 函数之后,我们的 coroutine.resume() 才会返回。

我们可以通过改造我们的例子来证明这一点:

local co = coroutine.create(function()
for i=1, 2 do
print(string.format("print in co %d",i))
coroutine.yield()
end
return "end"
end)
print(coroutine.resume(co))
print(coroutine.resume(co))
print(coroutine.resume(co))

执行我们代码的结果将会是:

print in co 1
true
print in co 2
true
true end

最后,有意思的是,当我们执行一个以及返回,而不是 yield 的协程时,会将协程的返回值作为 coroutine.resume() 的返回值。

coroutine.resume() 的返回值

前面的例子中,当协程函数返回的时候,其返回值也会传递给 coroutine.resume() ,那么,我们在调用 coroutine.yield() 的时候能有返回值呢?这是当然的,我们传递给 coroutine.yield() 的返回值,也会传递给 coroutine.resume()。


local co = coroutine.create(function()
for i=1, 2 do
print(string.format("print in co %d",i))
coroutine.yield("yield return",i)
end
return "end"
end)
print(coroutine.resume(co))
print(coroutine.resume(co))
print(coroutine.resume(co))

输出:

print in co 1
true yield return 1
print in co 2
true yield return 2
true end

coroutine.yield() 的返回

事实上,和 coroutine.resume() 一样,coroutine.yield() 也不会立即返回,为什么呢?因为调用 coroutine.yield() 的时候,已经让出了控制权。如例:

local co = coroutine.create(function()
for i=1, 2 do
print(string.format("co: %d",i))
coroutine.yield("toresume: ",i)
print("yield returned:",i)
end
return "end"
end)
print(coroutine.resume(co))
print(coroutine.resume(co))
print(coroutine.resume(co))

输出:

co: 1
true toresume: 1
yield returned: 1
co: 2
true toresume: 2
yield returned: 2
true end

coroutine.yield() 的返回值

同样,我们也会问,coroutine.yield() 有返回值么?肯定是有的,因为其在再次 coroutine.resume() 的时候才会返回,所以,其返回值,是由 coroutine.resume() 传递过去的。


local co = coroutine.create(function()
for i=1, 2 do
print(string.format("co: %d",i))
print("yield returned:",i,coroutine.yield("toresume: ",i))
end
return "end"
end)
print(coroutine.resume(co,"toyield 1"))
print(coroutine.resume(co, "toyield 2"))
print(coroutine.resume(co, "toyield 3"))

输出:

co: 1
true toresume: 1
yield returned: 1 toyield 2
co: 2
true toresume: 2
yield returned: 2 toyield 3
true end

我们看到,第一次调用 coroutine.resume() 的时候,并没有将值传递过去,因为这个时候并不是调用 coroutine.yield() 让出控制权的,而是建立之初就是挂起状态。

总结

协程间的交互,实际上就是通过简单的 coroutine.resume(), coroutine.yield() 传参来实现的。

我们传递给 coroutine.resume() 的参数,除首次运行外,都会作为 coroutine.yield() 的返回值;

而我们传递给 coroutine.yield() 的参数,也都会作为 coroutine.resume() 的返回值。