当前位置: 首页 > 工具软件 > 协同放置 > 使用案例 >

Lua 基础之协同程序

戚侯林
2023-12-01

协同程序

  • 协同程序类似于多线程中的线程。lua 提供 非对称的协同程序 ,即 lua 提供两个函数来控制协同程序的执行,一个用于挂起执行,另一个用于恢复执行。
  • 协同程序的函数放置在表 coroutine 中,创建时传入一个函数,生成一个 thread 类型的值
  • 协同程序的四种状态:挂起(suspended),运行(running)、死亡(dead),正常(normal)
  • 使用 status 函数来查看协程的状态,协程创建时的状态是 挂起
  • 使用 resume 函数可以唤醒协程,将其状态由 挂起 变为 运行
  • 如果协程执行一次就完了,那它的状态就会变成 死亡
  • 使用 yield 函数来挂起协程,将其状态由 运行 变为 挂起
  • 当协程 A 唤醒另一个协程 B 时,A 就处于一个特殊的状态,称为 正常
  • 协程的挂起操作发生在 yield 函数中,也就是说协程运行到 yield 函数时暂停,没有从 yield 函数返回,当下次执行 resume 的时候才从 yield 返回,然后继续执行,直到遇到下一次 yield 或协程运行结束
local mod = ...

local m = {}

_G[mod] = m
package.loaded[mod] = m

m.main = function()
    while coroutine.status(m.co) ~= [[dead]] do
        io.read()
        coroutine.resume(m.co)
    end
end

m.co = coroutine.create(
    function()
        for i = 1, 10 do
            print("co", i)
            coroutine.yield()
        end
    end
)

使用 resume-yield 交换数据

  • 第一次唤醒协程,resume 参数传给协程主函数
coroutine.resume(m.co2, 1, 2, 3)
coroutine.resume(m.co2, 10, 20, 30)

m.co2 = coroutine.create(
    function(a, b, c)
        while true do
            print("co", a, b, c)
            coroutine.yield()
        end
    end
)

co 1 2 3
co 1 2 3

从输出结果可以看出,第一次调用 resume 时,参数的值传给了 a,b,c,第二次调用时则不会

  • yield 传入的参数值传给 resume
print(coroutine.resume(m.co2, 1, 2, 3))
print(coroutine.resume(m.co2, 10, 20, 30))

m.co2 = coroutine.create(
    function(a, b, c)
        while true do
            print("co", a, b, c)
            coroutine.yield(a + b, a + c, b + c)
        end
    end
)

co 1 2 3
true 3 4 5
co 1 2 3
true 3 4 5

  • yield 的返回值是 resume 传入的参数
print(coroutine.resume(m.co2, 1, 2, 3))
print(coroutine.resume(m.co2, 10, 20, 30))

m.co2 = coroutine.create(
    function(a, b, c)
        while true do
            print("co", coroutine.yield())
        end
    end
)

true
co 10 20 30
true

第一次调用 resume 时,传入的参数 1,2,3 被参数 a,b,c 接收了,然后协程在 yield 函数中挂起,第二次调用 resume,传入的 10,20,30 作为 yield 的返回值,yield 返回到协程继续执行,输出 co 10 20 30

  • 协程运行完毕,主函数返回值传给 resume
print(coroutine.resume(m.co2, 1, 2, 3))
print(coroutine.resume(m.co2, 10, 20, 30))

m.co2 = coroutine.create(
    function(a, b, c)
        coroutine.yield()
        return a + b + c
    end
)

true
true 6

总结

  1. resume 第一次执行,参数传给协程主函数
  2. resume 再次执行,参数作为 yield 的返回值
  3. 协程挂起时 yield 的参数作为 resume 的返回值
  4. 协程死亡时主函数的返回值作为 resume 的返回值

生产者-消费者

生产者-消费者是一个经典的协同程序,生产者不停地生产值,然后发送给消费者;消费者不停地接收值,然后催促生产者生产;两者都有自己的主循环,都把自己当作调用的主动方,但实际程序只能有一方是主动方,而另一方则必须由主动方来唤醒,执行一次之后挂起,下面就是一个 消费者驱动 的例子

-- 生产者设计成协程
m.producer = coroutine.create(
    function()
        while true do
            local value = tonumber(io.read())
            if value == -1 then
                break
            end
            -- 生产者生产值之后挂起
            coroutine.yield(value)
        end
    end
)

-- 消费者驱动
m.customer = function()
    while true do
        -- 消费者唤醒生产者
        local status, value = coroutine.resume(m.producer)
        if status == false or coroutine.status(m.producer) == "dead" then
            break
        end
        io.write(value, "\n")
    end
end

可以在生产者和消费者之前加一个过滤器。生产者是一个协程,过滤器也是一个协程;然后消费者唤醒过滤器,过滤器再唤醒生产者,生产者产生值之后就挂起自己,过滤器接收生产的值后也挂起自己,最后消费者接收生产的值后继续唤醒过滤器

-- 生产者设计成协程
m.producer = function()
    return coroutine.create(
        function()
            while true do
                local value = io.read()
                if value == "-1" then
                    break
                end
                -- 生产者生产值之后挂起
                coroutine.yield(value)
            end
        end
    )
end

-- 过滤器设计成协程
m.filter = function(producer)
    return coroutine.create(
        function()
            for line = 1, math.huge do
                -- 唤醒生产者
                local status, value = coroutine.resume(producer)
                -- 生产者死亡
                if status == false or coroutine.status(producer) == "dead" then
                    break
                end
                x = string.format("%-5d %s", line, value)
                -- 过滤器得到生产值之后挂起
                coroutine.yield(x)
            end
        end
    )
end

-- 消费者驱动
m.customer = function(filter)
    while true do
        -- 唤醒过滤器
        local status, value = coroutine.resume(filter)
        -- 过滤器死亡
        if status == false or coroutine.status(filter) == "dead" then
            break
        end
        io.write(value, "\n")
    end
end
 类似资料: