【Lua 入门基础篇(九)】协程(coroutine)

简介: 笔记

一、什么是协同程序?


Lua 协同程序(coroutine)与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。

Coroutines are computer program components that generalize subroutines for non-preemptive multitasking, by allowing execution to be suspended and resumed.

协同程序是一种计算机程序组件,它通过允许暂停和恢复执行,将子程序泛化以实现非抢占式的多任务处理。


二、协程 与 线程


线程是抢占式的,协程是非抢占式的。


一个多线程程序可以同时运行几个线程,而协程却需要彼此协作的运行。


在任一指定时刻只有一个协同程序在运行,并且在明确被要求挂起的时候才会挂起。


协程有点类似同步的多线程,在等待同一个线程锁的几个线程有点类似。


线程数量通常不可以太多,而协程的数量可以非常多。如果线程的数量太多,那么大量线程的切换会影响性能。


协程的调度是应用代码自己实现的,而线程的调度是操作系统实现的。


三、基本语法


2.png

协程拥有四种状态,分别是:


挂起(suspended):当一个协程刚被创建时或遇到函数中的yield关键字时

运行(running):当协程运行时

正常(normal):当协程处于活跃状态,但没有被运行(这意味着程序在运行另一个协程,比如从协程A中唤醒协程B,此时A处于正常状态,因为当前运行的是协程B)。

死亡(dead):当协程运行完要执行的代码时或者在运行代码时发生了错误(error)。


四、基本操作


1. 创建协程

创建协程可以使用coroutine.create方法,传入的参数是匿名函数,即要执行的函数代码,返回的是一个新的协程。

co = coroutine.create(
  function()
    print('lua')
    end
)
print(coroutine.status(co)) -- suspended

2. 执行协程

新创建的协程并不会运行,而是处于挂起状态,可以通过coroutine.resume让其运行。

Lua提供的是非对称协程(asymmertric coroutine),需要两个函数来控制协程的运行,一个用于挂起协程的执行,另一个用于恢复它的执行。

co = coroutine.create(
  function()
    print(coroutine.status(co)) -- running
    print('lua')
    end
)
coroutine.resume(co) -- lua
print(coroutine.status(co)) -- dead(函数体运行结束,协程死亡)

3. 挂起协程

它强大的功能在于yield函数,这个函数可以让一个运行中的协程挂起自己,然后通过resume函数让其恢复运行。

co = coroutine.create(
  function()
    coroutine.yield() -- 协程挂起
    end
)
coroutine.resume(co)
print(coroutine.status(co)) -- suspended

4. 报错信息

co = coroutine.create(
  function()
    print('co')
    end
)
coroutine.resume(co)
print(coroutine.status(co)) -- dead
coroutine.resume(co) -- 没任何输出

当协程运行完毕后再调用resume,并没有输出任何信息。


协程运行完所有代码后会进入死亡状态,resume一个死亡的协程不会出错吗?


因为函数resume和pcall一样,都是运行在保护模式中的。如果协程中出现错误,Lua语言并不会显示错误信息,而是将错误返回给函数resume。

co = coroutine.create(
  function()
    print('co')
    end
)
coroutine.resume(co)
print(coroutine.resume(co))
-- false  cannot resume dead coroutine

5. wrap 创建协程

除了函数create,还可以使用wrap来创建一个协程,不过它返回的是一个函数,而不是协程。并且如果协程中出现错误,会中止协程并且将错误抛出

wrap = coroutine.wrap(
    function()
        for i = 1, 2 do
            print(i)
            coroutine.yield()
        end
    end
)
wrap() -- 1, 然后yield
wrap() -- 2, yield
wrap() -- 此时status=suspended,所以这里调用才算结束协程所有代码块
wrap() -- cannot resume dead coroutine

6. 返回值

  • resume的参数是协程中主函数的参数。
  • resume的返回值是协程主函数的返回值
co = coroutine.create(
    function(a, b)
        return a, b
    end
)
print(coroutine.resume(co, 1, 2))
-- true 1 2

被挂起,那么resume就接收不到主函数的返回值,仅输出true。

co = coroutine.create(
    function(a, b)
        coroutine.yield()
        return a, b
    end
)
print(coroutine.resume(co, 1, 2))
-- true
print(coroutine.resume(co, 1, 2))
-- true 1 2
print(coroutine.resume(co, 1, 2))
-- false cannot resume dead coroutine
  • yield返回值是再次唤醒协程的resume中传进的参数。

第二次唤醒后,继续向下执行,return,仍然返回是的先前的a, b = 1, 2

co = coroutine.create(
    function(a, b)
        print(coroutine.yield())
        -- 3  4
        return a, b
    end
)
print(coroutine.resume(co, 1, 2))
-- true
print(coroutine.resume(co, 3, 4))
-- true 1 2

运行结果:

true
3 4
true 1 2

resume返回值:yield的参数

co = coroutine.create(
    function(a, b)
        coroutine.yield(a + b, a * b)
        return a - b
    end
)
print(coroutine.resume(co, 1, 9))
print(coroutine.resume(co, 'A', 'B'))
true 10 9
true -8

总结:


resume参数 = > =>=>(主函数参数、yield返回值)

resume返回值 < = <=<= (主函数返回值,yield参数)


五、demo


1. 协程运行流程

function foo(a)
    print('foo : ' .. ' a = ' .. a)
    return coroutine.yield(2 * a)
end
co = coroutine.create(
    function(a, b)
        print(a, b)
        local r = foo(a + 1)
        print('co : ' .. '  r = ' .. r)
        local r, s = coroutine.yield(a + b, a - b)
        print('co : ' .. ' r, s = ' ..  r .. ', ' .. s)
        print('co : ' .. ' b = ' .. b)
        return b
    end
)
print('main : ', coroutine.resume(co, 1, 2))
print(" ======================== ")
print('main : ', coroutine.resume(co, "A", 'B'))
print(" ======================== ")
print('main : ', coroutine.resume(co, '!', '?'))
print(" ======================== ")
print('main : ', coroutine.resume(co, 'g', 'g'))
print(" ======================== ")

运行截图如下:

如上分析,那么这里r=A其实是return A, B,但r只能接住A

3.png

2. 生产-消费者问题

创建一个生产工厂,让它生产20件产品,每生产一件就把协程挂起,等待客户下一次提交需求的时候才重新resume唤醒

local newProductor
function productor()
    local i = 0
    while true do
        i = i + 1
        send(i) -- 将生产的物品发送给消费者
    end
end
function consumer()
    local i = receive()
    while i < 20 do
        print(i)
        i = receive()
    end
end
function receive()
    local status, value = coroutine.resume(newProductor)
    return value
end
function send(x)
    coroutine.yield(x)
    -- x表示需要发送的物品,就挂起该协同程序
end
-- 创建生产工厂
newProductor = coroutine.create(productor)
consumer()

六、对称(symmetric) 与 非对称(asymmetric)


下图表示的就是对称协程,进入到该协程之后只能有一个操作就是yield,把cpu让回给调度器。

4.png

下图表示非对称协程,可以有两个操作,就是resume和yield,从哪里resume的,yield回哪

5.png

相关文章
|
12月前
|
C# Python
[√]lua 协程
[√]lua 协程
67 1
|
3月前
|
存储 Linux 调度
协程(coroutine)的原理和使用
协程(coroutine)的原理和使用
|
3月前
Lua语法(四)——协程
Lua语法(四)——协程
41 0
|
5月前
|
PHP 调度 容器
Swoole 源码分析之 Coroutine 协程模块
协程又称轻量级线程,但与线程不同的是;协程是用户级线程,不需要操作系统参与。由用户显式控制,可以在需要的时候挂起、或恢复执行。
78 1
Swoole 源码分析之 Coroutine 协程模块
|
5月前
|
调度 C++ 开发者
C++一分钟之-认识协程(coroutine)
【6月更文挑战第30天】C++20引入的协程提供了一种轻量级的控制流抽象,便于异步编程,减少了对回调和状态机的依赖。协程包括使用`co_await`、`co_return`、`co_yield`的函数,以及协程柄和awaiter来控制执行。它们适合异步IO、生成器和轻量级任务调度。常见问题包括与线程混淆、不当使用`co_await`和资源泄漏。例如,斐波那契生成器协程展示了如何生成序列。正确理解和使用协程能简化异步代码,但需注意生命周期管理。
90 4
|
5月前
|
监控 程序员 调度
协程实现单线程并发(入门)
协程实现单线程并发(入门)
58 1
|
4月前
|
存储 调度 Python
异步编程概述在 Python中,`asyncio`库提供了对异步I/O、事件循环、协程(coroutine)和任务的支持。
异步编程概述在 Python中,`asyncio`库提供了对异步I/O、事件循环、协程(coroutine)和任务的支持。
|
5月前
|
图形学
【unity知识点】Unity 协程/携程Coroutine
【unity知识点】Unity 协程/携程Coroutine
318 0
|
6月前
|
调度 Python
什么是Python中的协程(Coroutine)?如何使用`async`和`await`进行协程编程?
什么是Python中的协程(Coroutine)?如何使用`async`和`await`进行协程编程?
74 0