1

最近学习Luacoroutine章节时,总对其使用场景及其意义不明白,自己也试着写了一些代码,但仍然没搞明白。

下面是我写的一段模拟下载网页的程序,使用协程和不使用协程耗时几乎相同,实在没能理解coroutine的非阻塞特性的价值。

-- 本例模拟耗时处理,如下载资源等,使用coroutine优化

local queue = {}

function insert( url )
    local co = coroutine.create(function ( ... )
        handle(url)
    end)
    table.insert(queue ,co)
end

-- 代表下载网页逻辑,这里使用休眠函数模拟
function handle( url )
    sleep(1)
    print('下载完成:' ,url)
end

-- 模拟休眠函数
function sleep(n)
  if n > 0 then 
    os.execute("ping -n " .. tonumber(n+1) .. " localhost > NUL")
  end
end

function start( ... )
    for _ ,co in ipairs(queue) do
        local status ,res = coroutine.resume(co)
        print(status ,res)
    end
end

insert('/image/1.png')
insert('/image/2.png')
insert('/image/3.png')

start()

-- XXX 测试 coroutine 并不能提升响应时间,如果体现其非阻塞特性?

-- handle('/image/1.png')
-- handle('/image/2.png')
-- handle('/image/3.png')

程序使用休眠代替实际下载逻辑,实际测试下来使用协程和不使用协程耗时并无区别,除了调用过程更加复杂,似乎没感觉到什么区别。当然非阻塞的价值也许并不体现在耗时优化上,但看很多socket库使用coroutine来优化,没太理解这样做的用意,如果说有10个下载任务,单线程上再怎么异步也不能提升性能吧。

有精通这块的,烦请给稍微讲解一下,后面也准备去读一些开源库的源码研究一下。

2016-12-17 提问
1 个回答
1

已采纳

用协程优化这种网络操作其实原理类似于一个脑筋急转弯:“用一个锅煎1个鸡蛋要3分钟,请问煎3个鸡蛋要几分钟?”
对于网络库而言,如果网络API用的是阻塞API的话,recv操作会阻塞到有合适的缓冲区内容为止,但是recv操作本身并不是用来准备缓冲区的操作,意思就是如果 recv 操作晚于网卡接收数据,也能正确的从缓存获取数据,因而如果在send操作之后就yield当前的coroutine,然后去做一些其他的事(比如再开几个连接),等一会儿再resume回来进行recv操作就可以提高并发度。
相当于说是从:

-- 同时只能处理1个socket
for _, sock in ipairs(socks) do
    send(sock, ...)
    recv(sock, ...)
end

变成:

for _, sock in ipairs(socks) do
    send(sock, ...)
end
-- 同时处理多个socket
for _, sock in ipairs(socks) do
    recv(sock, ...)
end

当然单是这样的话提高的并发度非常有限,要真正提高并发度到一个实用的水准还是得用IO多路复用。

撰写答案

推广链接