Lua 协程中 resume 和 yield 函数的混淆

我正在通过这个 视频教程 学习 Lua。其中有这样一段代码:

co = coroutine.create(function()
    for i=1,5 do
      print(coroutine.yield(i))
    end
  end)

print(coroutine.resume(co,1,2))
print(coroutine.resume(co,3,4))
print(coroutine.resume(co,5,6))
print(coroutine.resume(co,7,8))
print(coroutine.resume(co,9,10))
print(coroutine.resume(co,11,12))

输出结果如下:

true    1
3   4
true    2
5   6
true    3
7   8
true    4
9   10
true    5
11  12
true

但是我不理解 yield 和 resume 是如何互相传递参数的,以及为什么 yield 没有输出传递给它的第一组参数 1 和 2 。能否有人解释一下?谢谢!

点赞
用户3677376
用户3677376

普通的 Lua 函数有一个入口(用于传入参数),一个出口(用于传出返回值):

local function f( a, b )
  print( "arguments", a, b )
  return "I'm", "done"
end

print( "f returned", f( 1, 2 ) )
--> arguments       1       2
--> f returned      I'm     done

将参数放在括号内将其与参数名(本地变量)绑定,列表中作为 return 语句的一部分的返回值可以通过在赋值语句的右侧将函数调用表达式放入,或者在大型表达式(例如另一个函数调用)中使用。

还有其他调用函数的方法。例如,pcall() 调用函数并捕获可能引发的运行时错误。将参数放在 pcall() 函数调用中(在函数名称之后)作为参数传递。pcall() 也会添加一个额外的返回值,用于表示函数正常退出或通过错误退出。被调用函数内部不变。

print( "f returned", pcall( f, 1, 2 ) )
--> arguments       1       2
--> f returned      true    I'm     done

您可以使用 coroutine.resume() 而不是 pcall() 调用协程的主函数。参数传递方式和额外的返回值保持不变:

local th = coroutine.create( f )
print( "f returns", coroutine.resume( th, 1, 2 ) )
--> arguments       1       2
--> f returns       true    I'm     done

但是,在使用协程时,您还可以(临时)退出函数的另一种方式:coroutine.yield()。您可以通过将其作为参数放入 yield() 函数调用中使用 coroutine.yield() 传递值。这些值可以在 coroutine.resume() 调用的返回值中作为 yield() 函数调用的返回值而不是正常的返回值在外部检索。

但是,您可以通过再次调用 coroutine.resume() 重新进入中止的协程。协程在离开的地方继续进行,传递给 coroutine.resume() 的额外值可以作为先前挂起协程的 yield() 函数调用的返回值之后作为返回值。

local function g( a, b )
  print( "arguments", a, b )
  local c, d = coroutine.yield( "a" )
  print( "yield returned", c, d )
  return "I'm", "done"
end

local th = coroutine.create( g )
print( "g yielded", coroutine.resume( th, 1, 2 ) )
print( "g returned", coroutine.resume( th, 3, 4 ) )
--> arguments       1       2
--> g yielded       true    a
--> yield returned  3       4
--> g returned      true    I'm     done

请注意,yield 不必直接放在协程的主函数中,它可以在嵌套的函数调用中。执行跳回到最初(重新)启动协程的 coroutine.resume()

现在回答您的问题,为什么第一个 resume() 中的 1,2 没有出现在输出中:您的协程主函数没有列出任何参数,因此忽略所有传递给它的参数(在第一次函数进入时)。同样,由于您的主函数没有返回任何返回值,因此最后的 resume() 除了指示成功执行的 true 之外不返回任何额外的返回值。

2016-06-28 12:11:02
用户2858170
用户2858170
co = coroutine.create(function()
    for i=1,5 do
      print(coroutine.yield(i))
    end
  end)

我们使用以下代码第一次启动协程:

print(coroutine.resume(co,1,2))

它将运行到第一个yield,我们的第一次resume调用将返回true和yield的参数(这里i = 1),这解释了第一行输出。

我们的协程现在被挂起。一旦我们第二次调用resume:

print(coroutine.resume(co,3,4))

您的第一个yield最终返回,当前resume的参数(3,4)将被打印。循环的第二次迭代开始,coroutine.yield(2)被调用,暂停协程,再次使您的最后一个resume返回true,2等等。

因此,在您的示例中,coroutine.resume(co)足以进行第一次调用,因为任何进一步的参数都会丢失。

2016-06-28 12:16:53
用户10668518
用户10668518

我们看到这种行为的原因是微妙的,但这与 "进入" 和 "退出" yield 语句不匹配有关。它还与匿名函数内 printyield 调用的顺序有关。

让我们想象一张 print(coroutine.yield(i)) 和迭代执行的图。

在第一次迭代中,我们使用 coroutine.resume12 传递给协程。这是起始点,所以我们没有从以前的 yield 中提取代码,而是从匿名函数本身的原始调用开始。yieldprint 中调用,返回 i=1,但未调用 print。该函数退出。

接下来,在暂停一段时间后,我们看到下一个 coroutine.resume 的恢复。该 resume 传递 34。该函数从最后一个 yield 开始。请记住,在第一次调用 yield 时,print 函数未被调用,而是首先调用了 yield。那么执行返回到 print 中,所以现在调用了 print,但这次返回 34,因为这些是最新的传输值。该函数再次重复,先调用 yield,再调用 print,返回 i=2

如果我们进入迭代,我们会更加确定共同模式,这就是我们为什么没有看到 12 的原因。我们的第一个迭代是一个与相应的 "进入 yield" 不匹配的 "退出 yield"。这对应于协程 co 的第一次执行时间。

我们可能希望最后一个 yield 也不匹配,但区别在于我们会有一个未配对的 "进入" yield,而不是未配对的 "退出" yield,因为循环已经完成。这就解释了为什么我们看到 11 12,随后是 true,而没有任何 "退出 yield"。

这种情况独立于 for 循环内的奇偶性。重要的是 resumeyield 对以及它们的处理方式。您必须欣赏,yield 在第一次调用 resume 时不会在函数内返回值,因为该 resume 用于在协程内调用该函数。

2020-05-27 00:08:58