Lua协程

Lua协程

 

协同程序(coroutine)与多线程状况下的线程比较相似:有本身的堆栈、局部变量、指令指针,但与其它协程共享全局变量等不少信息。html

协程相似一种多线程,但与多线程还有不少区别:服务器

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

 

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

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

 

一、状态切换

协程有3个状态:函数

一、suspended(挂起);ui

二、running(运行);lua

三、dead(中止);spa

 

协程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。

 

二、管道

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

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的方式下,每一个任务在独立的进程中运行,进程间的切换代价比较高;

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

 

三、非抢占式多线程

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

当一个协程正在运行时,不能在外部终止它,只能经过显式调用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()
相关文章
相关标签/搜索