浅谈Lua的Coroutine-协程的多"线程"并发模型

看了一下《Programming in Lua》里的协程程序的运用,总觉得有点像雾里看花一样,捉不到重点,不知道怎么去运用,但在洗澡时灵光一闪,突然想明白了这不只是使用了Reactor(反应时同步时间分派)模式吗。在这里写篇博客跟大家分享一些拙见。

先贴一下之前看得不怎么懂的源码

function download (host, file)          -->协同程序
    local c = assert(socket.connect(host, 80))
    local count = 0 -- counts number of bytes read
    c:send("GET " .. file .. " HTTP/1.0\r\n\r\n")
    while true do
        local s, status = receive©
        count = count + string.len(s)
        if status == "closed" then break end
    end
    c:close()
    print(file, count)
end    

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

threads = {}                               --> list of all live threads
function get (host, file)                --> 工厂函数
    local co = coroutine.create(function ()
    download(host, file)
    end)
    table.insert(threads, co)           --> insert it in the list
end

function dispatcher ()                   -->分派器函数    
    while true do
      local n = table.getn(threads)
      if n == 0 then break end  
      for i=1,n do
      local status, res = coroutine.resume(threads[i])
      if not res then    
        table.remove(threads, i)
        break
      end
    end
  end
end

首先来看一下

  dispatcher函数实际上充当了Reactor模式中的select和poll/epoll

  get这个工厂函数充当了向分派器注册“线程”(协同程序)的作用

  download与receive这两个函数在Reactor模式中共同起到一个线程的作用

  不同点是:1、以上Lua代码中通过额外的恢复/挂起操作代替了Reactor模式中的分派器检测系统资源来确定线程是否活动

       2、因为分派器与各“线程”(协同程序)的执行实际上都在一个线程中执行,不能利用系统在真正线程之间分时切换,必须手动实现切换。

  Coroutine-协程模型的优点是:

       1、切换上下文的代价比较小。

       2、不需要考虑同步问题。  

       

实际上按照Reactor模式使用多协程程序很简单

  只需要实现一个全局的分派器,开放注册“线程”的接口。写需要的协同程序(必须包含一个中断条件),并注册到分派器。

  运行分派器就可以实现简单的多线程并发。当然这只是一个最简单的模型,只能在当至少有一个“线程”有数据可读取的时候运行比较良好,如果各个“线程”都处于空闲状态,则分配器会不断轮询线程状态,导致消耗大量时间。