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


    在这里插入图片描述
    在这里插入图片描述

    一、什么是协同程序?

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

    Coroutines are computer program components that generalize subroutines for non-preemptive multitasking, by allowing execution to be suspended and resumed.
    协同程序是一种计算机程序组件,它通过允许暂停和恢复执行,将子程序泛化以实现非抢占式的多任务处理。

    二、协程 与 线程

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

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

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

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

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

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

    三、基本语法

    在这里插入图片描述

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

    • 挂起(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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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(函数体运行结束,协程死亡)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3. 挂起协程

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

    co = coroutine.create(
    	function()
    		coroutine.yield() -- 协程挂起
        end
    )
    coroutine.resume(co)
    print(coroutine.status(co)) -- suspended
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4. 报错信息

    co = coroutine.create(
    	function()
    		print('co')
        end
    )
    coroutine.resume(co)
    print(coroutine.status(co)) -- dead
    coroutine.resume(co) -- 没任何输出
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    当协程运行完毕后再调用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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    6. 返回值

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

    被挂起,那么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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    • 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    运行结果:

    true
    3 4
    true 1 2
    
    • 1
    • 2
    • 3

    • 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'))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    true 10 9
    true -8
    
    • 1
    • 2

    总结:

    • 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(" ======================== ")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    运行截图如下:

    如上分析,那么这里r=A其实是return A, B,但r只能接住A
    在这里插入图片描述


    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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

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

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

    在这里插入图片描述

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

    在这里插入图片描述

  • 相关阅读:
    【selenium】使用 cookies
    医疗大数据系统
    Linux内核源码分析 1:Linux内核体系架构和学习路线
    项目知识点总结-过滤器-MD5注册-邮箱登录
    Spring Boot 的参数校验方案
    css自学框架之图片灯箱展示
    20240301-2-ZooKeeper面试题(二)
    线性反馈移位寄存器的输出(未解出)
    【Odoo】Odoo16-性能优化提升
    最新整理的linux基础命令大全 有需要的收藏
  • 原文地址:https://blog.csdn.net/qq_52678569/article/details/125762571