《lua程序设计》读书笔记 第9章:协同程序

协同程序与线程差不多,也就是一条执行序列,拥有自己独立的栈,局部变量和指令指针,同时又与其他协同程序共享全局变量和其他大部分东西。从概念上见,协同程序与线程的主要区别在于,一个具有多个线程的程序可以同时运行几个线程,但是一个具有多个协同程序的程序在任意时刻只能运行一个协同程序,当一个协同程序运行时,其他协同程序的则都处于挂起状态。

9.1 协同程序基础

Lua将所有关于协同程序的函数放置在一个名为“coroutine”的table中。函数create用于创建新的协同程序,它只有一个参数,就是一个函数,其返回一个thread类型的值,用以表示新的协同程序。

co = coroutine.create(function () print("hi") end)
print(type(co))  -->thread

一个协同程序有四种状态:挂起(suspended)、运行(running)、死亡(dead)、正常(normal)。
可以通过函数status来检查协同程序的状态:

print(coroutine.status(co) --> suspended

函数coroutine.resume用于启动或再次启动一个协同程序的执行,并将其状态由挂起改为运行, 协同程序执行完了之后状态变为dead:

coroutine.resume(co)         -->hi(调用函数输出hi)
print(coroutine.status(co))  -->dead

协同程序真正强大的地方在于函数yeild的使用上,该函数可以让一个运行中的协同程序挂起,而之后可以再恢复它的运行。

    co = coroutine.create(function()
        for i = 0, 10 do
            print("co", i)
            coroutine.yield()
        end
    end)
    coroutine.resume(co)    -->co, 1(开始执行,直到第一个yield)
    coroutine.resume(co)    -->co, 2
    ...
    coroutine.resume(co)    -->co, 10(开始执行,直到第一个yield)
    coroutine.resume(co)    -->什么都不打印
    print(coroutine.resume(co))  -->false(此时该协同程序已处于死亡状态,将返回false以及一条错误信息)

请注意resume是在保护模式下运行的,因此,如果一个协同程序的执行中发生任何错误,Lua是不会显示错误信息的,而是将执行权返回给resume调用。
当一个协同程序A唤醒另一个协同程序B时,协同程序A就处于一个特殊状态,既不是挂起状态也不是运行状态,而是“正常”状态(normal)。
Lua协同程序可以通过一对resume-yield来交换数据。当调用resume时,所有传递给resume的额外参数都视为协同程序主函数的参数。

co = coroutine.create(function (a,b,c)
            print(a, b, c)
        end)
coroutine.resume(co, 1, 2, 3)    -->co 1 2 3

而对于resume函数的返回值,第一个值为bool值表示有没有错误,而后面所有的返回值都是对应yield传入的参数:

    co = coroutine.create(function(a, b)
        coroutine.yield(a+b, a-b)
        end)
    print(coroutine.resume(co, 20, 10) -->true, 30, 10

9.2 管道(pipe)与过滤器(filter)

一个关于协同程序的经典示例就是“生产者–消费者”的问题。协同程序被称为是一种匹配生产者和消费者的理想工具。通常这两个函数如下:

function producer()
    while true do
        local x = io.read()      //产生新的值
        send(x)                  //发送给消费者
    end
end

function consumer()
    while true do
        local x = receive()      //从生产者接受值
        io.write(x,"\n")         //消费新值
    end
end

当一个协同程序调用yield时,它不是进入一个新的函数,而是从一个悬而未决的resume调用中返回,对于resume调用也不是启动一个函数,而是从一次yield调用中返回,这项特性正可用于匹配send和receive,这两者均认为自己是主动方,对方是被动方。

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

function send(x)
    coroutine.yield(x)
end
//生产者是一个协同程序
producer = coroutine.create(
        function()
            while true do
                local x = io.read()
                send(x)
            end
        end)

在这种设计中,程序通过调用消费者来启动。消费者需要一个新值时,它唤醒生产者,生产者返回一个新值后停止运行。这种设计称为“消费者驱动”。
还可以扩展上述设计,实现“过滤器”。过滤器是一种位于生产者和消费者之间的处理器,可用于对数据的一些交换。过滤器即是一个消费者也是一个生产者,它唤醒一个生产者产生新值,然后又将值传递给消费者。

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

function send(x)
    coroutine.yield(x)
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()
        for line = 1, math.huge do
            local x = receive(prod)      //获取新值
            x = string.format("%5d%s", line, x)
            send(x)       //发送新值
        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)

9.3 以协同程序实现迭代器

将循环迭代器视为“生产者–消费者”模式的一个特例,一个迭代器会产生一些内容,而循环体会消费这些内容。下面我们写一个可以遍历某个数组的所有排列组合形式的迭代器:

//先写一个生成所有排列组合的函数,想法很简单,只要将每个数组元素都依次放到最后一个位置,然后递归生成其余元素的排列
function permgen(a, n)
    n = n or #a
    if n < 1 then
        printResult(a)
    else
        for i = 0, n do
            a[n], a[i] = a[i], a[n]
            permgen(a, n - 1)
            a[n], a[i] = a[i], a[n]
        end
    end
end

function printResult(a)
    for i = 1, #a do
        io.write(a[i], " ")
    end
    io.write("\n")
end

//然后将生成函数改为一个迭代器
function permgen(a, n)
    n = n or #a
    if n <= 1 then
        coroutine.yield(a)
    else
        for i = 0, n do
            a[n], a[i] = a[i], a[n]
            permgen(a, n - 1)
            a[n], a[i] = a[i], a[n]
    end
end
//然后定义一个工厂函数,用于将生成函数放到一个协同程序中运行
function permutations(a) =
    local co = coroutine.create(function() permgen(a) end)
    return function()
        local code, res = coroutine.resume(co)
        return res
    end
end

permutations函数使用了一种在Lua中比较常见的模式,就是将一条唤醒协同程序的调用包装在一个函数中,为此Lua专门提供了一个函数coroutine.wrap来完成这个功能,类似于create,wrap创建一个新的协同程序。但不同的是,wrap并不是返回一个协同程序,而是返回一个函数,每次调用这个函数,就会唤醒协同程序一次。但这个函数与resume的不同之处在于,它不会返回错误代码。所以permutations可以这样写:

function permutations(a)
    return coroutin.wrap(function() permgen(a) end)
end

9.4 非抢占式的多线程

协同程序是非抢占式的,即当一个协同程序运行时,则无法从外部停止它。只有当协同程序显示要求挂起时(yield)才会停止。要实现抢占式的系统,请查看9.4节的示例,在此略过。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值