协程教程 |
|
协程允许我们同时执行多个任务。这是通过将控制权传递给每个例程并等待例程表示已完成来实现的。我们可以重新进入例程以在稍后继续,通过重复此操作,我们实现了多任务处理。
协程在参考手册的 2.11 和 5.2 节中进行了描述。 [1] [2]
每个任务都在一个线程中运行,该线程与其他线程分离。同时运行多个任务通常称为多线程。由于存在多个线程同时运行,因此我们的应用程序被称为多线程。
有多种方法可以实现多线程。一些系统为每个线程分配固定时间,并在时间到期时夺取控制权,并将控制权传递给下一个线程等。这称为抢占式多线程。在这种情况下,每个线程都不需要担心它占用了多少时间,它更关心自己的功能。
在其他系统中,线程确实关心它花费了多长时间。线程知道它必须将控制权传递给其他线程,以便它们也能正常运行。这称为协作式或协作式多线程。在这里,所有线程协同工作以使应用程序正常运行。这是 Lua 协程使用的多任务处理类型。
Lua 中的协程不是操作系统线程或进程。协程是在 Lua 中创建的 Lua 代码块,它们具有自己的控制流,就像线程一样。一次只有一个协程运行,它会一直运行,直到它激活另一个协程或让出(返回到调用它的协程)。协程是一种以方便自然的方式表达多个协作控制线程的方法,但不会并行执行,因此不会从多个 CPU 中获得性能优势。但是,由于协程切换比操作系统线程快得多,并且通常不需要复杂且有时昂贵的锁定机制,因此使用协程通常比使用完整操作系统线程的等效程序更快。
为了让多个协程共享执行,它们必须停止执行(在执行完合理的处理量后),并将控制权传递给另一个线程。这种提交行为称为让步。协程显式调用 Lua 函数 `coroutine.yield()`,这类似于在函数中使用 `return`。让步与函数返回的区别在于,我们可以在稍后重新进入线程并从我们离开的地方继续执行。当您使用 `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
要创建一个协程,我们必须有一个代表它的函数,例如:
> function foo() >> print("foo", 1) >> coroutine.yield() >> print("foo", 2) >> end >
我们使用 `coroutine.create(fn)` 函数创建一个协程。我们向它传递一个线程的入口点,它是一个 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(co) foo 1 true
> = coroutine.resume(co) foo 2 true
> = coroutine.status(co) dead
> = coroutine.resume(co) false cannot resume dead coroutine
以下是展示协程的一些重要特性的更复杂的示例。
> 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)
调用协程。第一次调用时,我们进入协程函数中的for
循环。请注意,由协程函数调用的函数odd()
会产生。您不必在协程函数中产生。这是一个重要且有用的功能。我们使用 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 返回 -1。
---- 3 A: odd 3 -- odd() yields again after resuming in for loop E: errorfree, value, status true 3 suspended
for
循环中恢复协程,当调用odd()
时,它再次产生。
---- 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()
中恢复我们离开的地方。请注意,变量值被保留。odd()
函数的作用域在协程挂起期间被保留。我们遍历到even()
的末尾,这次在函数末尾退出。无论哪种情况,当我们退出函数而不使用coroutine.yield()
时,作用域及其所有变量都会被销毁。只有在产生时,我们才能恢复。
---- 5 B: odd 5 -- odd called again E: errorfree, value, status true nil dead -- for loop terminates >
odd()
中恢复。这次,主for
循环达到了我们传递给协程的 5 的限制。值 5 和for
循环状态在整个协程执行过程中被保留。协程在存在期间保留自己的堆栈和状态。当我们退出协程函数时,它会死亡,我们不能再使用它。