Coroutines 教程 |
|
Coroutines 允许我们一次执行多个任务。这是通过将控制权传递给每个 routine 并等待 routine 表示完成来实现的。我们可以稍后重新进入 routine 继续执行,通过重复此过程,我们实现了多任务处理。
Coroutines 在参考手册的 2.11 和 5.2 章节中有描述。[1] [2]
每个任务都在一个独立的线程中运行。同时运行多个任务通常称为多线程。因为同时运行的线程不止一个,所以我们的应用程序被认为是多线程的。
实现多线程有多种方式。有些系统为每个线程分配固定的时间,并在时间结束时剥夺控制权,然后将控制权传递给下一个线程等。这称为抢占式多线程。在这种情况下,每个线程不需要担心占用多少时间,它更关心自己的功能。
在其他系统中,线程确实关心自己花费了多长时间。线程知道它必须将控制权传递给其他线程,以便它们也能正常工作。这称为协作式或协同多线程。在这里,所有线程都在协作,以使应用程序正常运行。这是 Lua coroutines 所使用的多任务类型。
Lua 中的 Coroutines 不是操作系统线程或进程。Coroutines 是在 Lua 内部创建的代码块,它们拥有自己的控制流,就像线程一样。一次只有一个 coroutine 在运行,它会运行直到激活另一个 coroutine,或者 yield(返回到调用它的 coroutine)。Coroutines 是一种以方便自然的方式表达多个协作控制流的方法,但它们不会并行执行,因此从多个 CPU 中无法获得性能提升。然而,由于 coroutines 的切换速度比操作系统线程快得多,并且通常不需要复杂且有时昂贵的锁定机制,因此使用 coroutines 通常比使用完整 OS 线程的等效程序更快。
为了让多个 coroutines 共享执行,它们必须停止执行(在执行了合理数量的处理后)并将控制权传递给另一个线程。这种提交行为称为yielding。Coroutines 显式调用 Lua 函数 coroutine.yield(),这类似于在函数中使用 return。Yielding 与函数返回的区别在于,稍后我们可以重新进入线程并从上次中断的地方继续。当您使用 return 退出函数作用域时,作用域将被销毁,我们无法重新进入它,例如:
> function foo(x) >> if x>3 then return true end -- we can exit the function before the end if need be >> return false -- return a value at the end of the function (optional) >> end > = foo(1) false > = foo(100) -- different exit point true
要创建一个 coroutine,我们必须有一个代表它的函数,例如:
> function foo()
>> print("foo", 1)
>> coroutine.yield()
>> print("foo", 2)
>> end
>
我们使用 coroutine.create(fn) 函数创建 coroutine。我们将一个 Lua 函数作为线程的入口点传递给它。Lua 返回的对象是一个线程。
> co = coroutine.create(foo) -- create a coroutine with foo as the entry > = type(co) -- display the type of object "co" thread
我们可以使用 coroutine.status() 函数查看线程的状态,例如:
> = coroutine.status(co) suspended
coroutine.resume() 函数。Lua 将进入线程,并在线程 yield 时退出。 > = coroutine.resume(co) foo 1 true
coroutine.resume() 函数返回 resume 调用的错误状态。输出确认我们进入了函数 foo,然后无错误地退出。现在是有趣的部分。对于函数,我们无法从上次中断的地方继续,但对于 coroutines,我们可以再次 resume。> = coroutine.resume(co) foo 2 true
foo 中的 yield 语句之后的行执行了,并且再次无错误返回。但是,如果我们查看状态,可以看到我们已经退出了函数 foo,并且 coroutine 已终止。> = coroutine.status(co) dead
> = coroutine.resume(co) false cannot resume dead coroutine
以下是一个更复杂的示例,演示了 coroutines 的一些重要特性。
> function odd(x)
>> print('A: odd', x)
>> coroutine.yield(x)
>> print('B: odd', x)
>> end
>
> function even(x)
>> print('C: even', x)
>> if x==2 then return x end
>> print('D: even ', x)
>> end
>
> co = coroutine.create(
>> function (x)
>> for i=1,x do
>> if i==3 then coroutine.yield(-1) end
>> if i % 2 == 0 then even(i) else odd(i) end
>> end
>> end)
>
> count = 1
> while coroutine.status(co) ~= 'dead' do
>> print('----', count) ; count = count+1
>> errorfree, value = coroutine.resume(co, 5)
>> print('E: errorfree, value, status', errorfree, value, coroutine.status(co))
>> end
---- 1
A: odd 1
E: errorfree, value, status true 1 suspended
---- 2
B: odd 1
C: even 2
E: errorfree, value, status true -1 suspended
---- 3
A: odd 3
E: errorfree, value, status true 3 suspended
---- 4
B: odd 3
C: even 4
D: even 4
A: odd 5
E: errorfree, value, status true 5 suspended
---- 5
B: odd 5
E: errorfree, value, status true nil dead
>
基本上,我们有一个 for 循环,它调用两个函数:当遇到奇数时调用 odd(),当遇到偶数时调用 even()。输出可能有点难以消化,所以我们将逐一研究由 count 计数的外部循环。已添加注释。
---- 1 A: odd 1 -- yield from odd() E: errorfree, value, status true 1 suspended
coroutine.resume(co, 5) 调用我们的 coroutine。第一次调用时,我们进入 coroutine 函数中的 for 循环。请注意,由我们的 coroutine 函数调用的 odd() 函数会 yield。您不一定需要在 coroutine 函数中 yield。这是一个重要且有用的特性。我们通过 yield 返回值 1。
---- 2 B: odd 1 -- resume in odd with the values we left on the yield C: even 2 -- call even and exit prematurely E: errorfree, value, status true -1 suspended -- yield in for loop
for 循环 yield 并挂起 coroutine。需要注意的一点是,我们可以随时 yield。我们不必一直在 coroutine 的一个点 yield。我们通过 yield 返回 -1。
---- 3 A: odd 3 -- odd() yields again after resuming in for loop E: errorfree, value, status true 3 suspended
for 循环中 resume coroutine,当调用 odd() 时,它再次 yield。
---- 4 B: odd 3 -- resume in odd(), variable values retained C: even 4 -- even called() D: even 4 -- no return in even() this time A: odd 5 -- odd() called and a yield E: errorfree, value, status true 5 suspended
odd() 中从上次中断的地方 resume。请注意,变量值已保存。odd() 函数的作用域在 coroutine 挂起期间得以保留。我们继续执行到 even() 的末尾,这次在函数末尾退出。在任何一种情况下,当我们不使用 coroutine.yield() 而退出函数时,作用域及其所有变量都将被销毁。只有在 yield 时我们才能 resume。
---- 5 B: odd 5 -- odd called again E: errorfree, value, status true nil dead -- for loop terminates >
odd() 中 resume。这次,主 for 循环达到了我们传递给 coroutine 的限制 5。值 5 和 for 循环的状态在 coroutine 的整个执行过程中都得到了保留。Coroutine 在存在期间会保留自己的堆栈和状态。当我们的 coroutine 函数退出时,它就死亡了,我们无法再使用它。