Tizeng's blog Ordinary Gamer

Lua学习笔记3——进阶

2020-01-07
Tizeng
Lua

协程(Coroutine)

协程类似线程,有自己的局部变量,和其他协程共享全局变量,有自己的栈(?),和线程最主要的不同是程序会同时有多个线程在跑,但是协程不是,在任何时候都只能有一个协程在跑,而且它在被显式要求时才会被挂起。协程有三个状态:suspended、running、dead。关于协程的操作被封装在coroutine表中:

  • 用create创建协程,它的参数是一个函数(一般为直接定义的匿名函数),返回一个thread类型
  • resume开始或重启协程,参数为create出来的协程
  • yield将协程挂起,等待下一次的resume
  • 期间用status查看协程的状态,参数为create出来的协程

要注意的是resume会根据协程是否还能继续返回true或false,如果能继续执行则true后面还会返回yield中输入的参数。resume的参数会传递给用create创建的coroutine函数中,yield会返回其额外的参数。当协程结束时,其返回的值(如果有)也会变成resume的返回值。

local co = coroutine.create(function(i)
    print(coroutine.yield("yield args1", i, 1))
    print(i)
    print(coroutine.yield("yield args2", i, 2), "in yield 2")
    return "a", "b"
end)
print("1-----:", coroutine.resume(co, 4))
print("========")
print("2-----:", coroutine.resume(co, 10, 2, 3))
print("========")
print("3-----:", coroutine.resume(co, 5, 6, 7))

--[[ 输出
1-----:	true	yield args1	4	1
========
10	2	3
4
2-----:	true	yield args2	4	2
========
5	in yield 2
3-----:	true	a	b
]]

协程最常见的一个应用便是解决生产者-消费者问题(producer-consumer problem),我们将发送端(producer)定义成一个协程,当接收端(consumer)需要信息的时候,便resume这个协程,它经过若干处理后返回要发送的值,并将协程挂起,等待下一次请求,这种设计只在我们需要的时候才去调用producer,因此也称为消费者驱动(consumer-driven),下面是一个简单的实现,来自lua官网

function send(x)
    coroutine.yield(x)
end

local producer = coroutine.create(
    function()
        while true do 
            local x = io.read()
            send(x)
        end
    end)

function receive()
    local status, value = coroutine.resume(producer)
    return value
end

print(receive())

可以进一步对上面的结构进行扩展,增加一个filter处理数据,send和receive方法不变,将producer改为函数:

function receive (prod)
    local status, value = coroutine.resume(prod)
    return value
end

function producer()
    return coroutine.create(function()
        while true do 
            local x = io.read()
            send(x)
        end
    end)
end

function filter(prod)
    return coroutine.create(function()
        line = 1
        while true do 
            local x = receive(prod)
            x = string.format("%5d %s", line, x)
            send(x)
            line = line + 1
        end
    end)
end

function consumer(prod)
    while true do 
        local x = receive(prod)
        io.write(x, "\n")
    end
end

p = producer()
f = filter(p)
consumer(f)
--consumer(filter(producer()))

协程还有一种作为迭代器的用法,就是在循环的时候每次通过yield来结束本次迭代,比如在一个递归函数中:

function func2(a, n)
    if n == 0 then 
        -- xxx
        coroutine.yield(xxx)
    else
        -- ...
        func2(a, n - 1)
        -- ...
    end
end

function func1(a)
    local n = table.getn(a)
    local co = coroutine.create(function() func2(a, n) end)
    return function() -- iterator
        local code, res = coroutine.resume(co)
        return res
    end
end

-- 可以用wrap来代替上面的写法,它们是等价的
function func3(a)
    local n = table.getn(a)
    return coroutine.wrap(function() func2(a, n) end)
end

for p in func3{"a", "b", "c"} do 
    print(p) -- 这里的p就是func2里面返回的xxx
end

反射


Comments

Content