Lua协程

Lua协程

 

协同程序(coroutine)与多线程情况下的线程比较类似:有自己的堆栈、局部变量、指令指针,但与其它协程共享全局变量等很多信息。

协程类似一种多线程,但与多线程还有很多区别:

1. 协程并非os线程,所以创建、切换开销比线程相对要小。
2. 协程与线程一样有自己的栈、局部变量等,但是协程的栈是在用户进程空间模拟的,所以创建、切换开销很小。
3. 多线程程序是多个线程并发执行,也就是说在一瞬间有多个控制流在执行。而协程强调的是一种多个协程间协作的关系,只有当一个协程主动放弃执行权,另一个协程才能获得执行权,所以在某一瞬间,多个协程间只有一个在运行。
4. 由于多个协程时只有一个在运行,所以对于临界区的访问不需要加锁,而多线程的情况则必须加锁。
5. 多线程程序由于有多个控制流,所以程序的行为不可控,而多个协程的执行是由开发者定义的所以是可控的。

 

Lua的协程是不对称的(asymmetric coroutines),是指“挂起一个正在执行的协同函数” 和 “使一个被挂起的协程再次执行的函数”是不同的。

有些语言使用对称协同(symmetric coroutines),即使用同一个函数负责“执行与挂起间的状态切换”。

 

1、状态切换

协程有3个状态:

1、suspended(挂起);

2、running(运行);

3、dead(停止);

 

协程API: 

co = coroutine.create(function)                         -->创建协程,返回thread类型

coroutine.status(co)                                    -->检查协程状态

coroutine.resume(co)                                    -->恢复运行

coroutine.yield()                                       -->挂起

协程创建成功后,处于suspended状态,此时并未运行。

在协程中使用yield函数,可以让正在运行的代码挂起。

 一个例子:

co = coroutine.create(function()
            for i=1,5 do
                print("co", i)
                coroutine.yield()
            end
        end)
    
coroutine.resume(co)    -- 1
coroutine.resume(co)    -- 2
coroutine.resume(co)    -- 3
coroutine.resume(co)    -- 4
coroutine.resume(co)    -- 5
print(coroutine.resume(co) )   -- true
print(coroutine.resume(co) )   -- false cannot resume dead coroutine                                                                  

注意:resume运行在保护模式下,如果协程内部存在错误,Lua并不会抛出错误,而是把错误返回给resume函数。

 

协程的另一个作用是通过resume-yield来交换数据:

co = coroutine.create(function(a, b)
        coroutine.yield(a+b, a-b)
        return 6,7
        end)

print(coroutine.resume(co, 20, 10))     -- true 30 10
print(coroutine.resume(co))          -- true 6 7

可见,resume的参数会传递给协同函数,yield的参数会作为resume的返回值。并且,协程结束时的返回值也会传递给 resume。

 

2、管道

协同最具有代表性的例子是用来解决生产者-消费者问题,假定有一个函数不断地生产数据(比如从文件读取),另一个函数不断的处理这些数据(比如写到一个文件中),函数如下:

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

上面的代码中,生产者和消费者都在不停的循环,而对对方的状态一无所知,我们需要改变一下结构,使得两者能够协调工作。

以消费者驱动模型为例,一开始我们调用消费者,当消费者需要值时唤起生产者,生产者生产处数据后停止,直到消费者再次请求。

完整的示例代码如下:

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

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


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

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

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

consumer(filter(producer()))

上面这个例子的工作方式非常类似UNIX的管道(pipe),协程是一种非抢占式的多线程。

在pipe的方式下,每个任务在独立的进程中运行,进程间的切换代价比较高;

而在协同中,每个任务运行在独立的协同代码中,任务间的切换代价较小,与函数调用相当。

 

3、非抢占式多线程

协程是一种非抢占式的多线程,这句话的含义是:

当一个协程正在运行时,不能在外部终止它,只能通过显式调用yield挂起它的执行。

显然,非抢占式的多线程比较容易写,因为不需要考虑线程同步带来的bug。

非抢占式的多线程的弊端在于,不管什么时候,只要有一个线程调用一个阻塞操作(blocking operation),整个程序在阻塞操作完成之前都将停止。

协同的这种弊端有点让人难以忍受!

 

看一个多线程的例子,通过HTTP协议从远程服务器下载一些文件。

在下载过程中,如果遇到阻塞,挂起线程,并使用一个调度器去resume另一个线程。

require "luasocket"

function receive(connection)
    connection:timeout(0)   -- do not block
    local s,status=connection:receive(2^10)
    if status=="timeout" then
        coroutine.yield(connection)
    end 
    return s, status
end

function download(host, file)
    local c=assert(socket.connection(host, 80))
    local count = 0 
    c:send("GET" .. file .. " HTTP/1.0\r\n\r\n")
    while ture do
        local s, status=receive(c)
        count=count+string.len(s)
        if status=="closed" then break end    
    end 
    c:close()
    print(file, count)
end

threads = {}
function get(host, file)
    local co=coroutine.create(function()
                download(host, file)
            end)

    table.insert(threads, co) 
end

function dispatcher() while ture do local n=#threads if n==0 then break end local connections={} for i=1,n do local status,res=coroutine.resume(threads[i]) if not res then      -- finish table.remove(threads, i) break else  -- timeout table.insert(connections, res) end end if #connections == n then socket.select(connections) end end end host="http://news.163.com/" get(host, "/14/0330/17/9OJO5ML800014JB6.html") get(host, "/14/0330/08/9OIQKNS90001124J.html") get(host, "/14/0330/10/9OJ2M5PN000915BF.html") dispatch()

 

 

 

 

posted @ 2014-03-30 20:23  如果的事  阅读(2531)  评论(0编辑  收藏  举报