协程教程

lua-users home
wiki

什么是协程?

协程允许我们同时执行多个任务。这是通过将控制权传递给每个例程并等待例程表示已完成来实现的。我们可以重新进入例程以在稍后继续,通过重复此操作,我们实现了多任务处理。

协程在参考手册的 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()` 函数。Lua 将进入线程,并在线程让步时离开。
> = coroutine.resume(co)
foo     1
true
`coroutine.resume()` 函数返回 resume 调用的错误状态。输出确认我们进入了函数 `foo`,然后没有错误地退出。现在是有趣的部分。对于函数,我们无法从我们离开的地方继续执行,但对于协程,我们可以再次恢复
> = coroutine.resume(co)
foo     2
true
我们可以看到我们在 `foo` 中执行了 yield 之后的行,并且再次没有错误地返回。但是,如果我们查看状态,我们可以看到我们退出了函数 `foo`,并且协程终止了。
> = 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循环状态在整个协程执行过程中被保留。协程在存在期间保留自己的堆栈和状态。当我们退出协程函数时,它会死亡,我们不能再使用它。


最近更改 · 偏好设置
编辑 · 历史记录
最后编辑于 2017 年 8 月 12 日下午 11:51 GMT (差异)