在单线程的程序中,采取的是顺序执行方式。对于下载程序来说,单线程的效率是极其低的,原因是它只能在下载完一个文件后才可以读取该文件。当接收一个远程文件时,程序将大部分时间花费在等待数据接收上。更明确地说,将时间用在了对receive阻塞调用上。因此,如果一个程序可以同时下载所有文件的话,效率就会大大提升。当一个连接没有可用数据时,程序可用处理其它连接。
在Lua中,可用协同程序实现并发下载。可以为每个下载任务创建一个新的线程,只要一个线程无可用数据,它就可以将控制权切换到其他线程。
具体实现代码如下:
1、下载程序
require "socket"
function download(host, file)
local c = assert(socket.connect(host, 80))
local count = 0 -- 记录接收到的字节数
c:send("GET" .. file .. " HTTP/1.0\r\n\r\n")
while true do
local s, status, partial = receive(c)
count = count + #(s or partial)
if status == "closed" then break end
end
c:close()
print(file, count)
end
负责连接到远程站点,发送下载文件的请求,控制数据的接收、处理、连接关闭等。
2、接收函数
function receive(connection)
connection:settimeout(0) -- 使receive调用不会阻塞
local s,status,partial = connection:receive(2^10)
if status == "timeout" then
coroutine.yield(connection)
end
return s or partial, status
end
对settimeout调用可使以后所有对此连接进行的操作不会阻塞。若一个操作返回的status为"timeout(超时)",就表示该操作在返回时还未完成。此时,线程就会挂起。
3、调度程序
threads = {} -- 用于记录所有正在运行的线程
function get(host, file)
-- 创建协同程序
local co = coroutine.create(function ()
download(host, file)
end)
-- 将其插入记录表中
table.insert(threads, co)
end
function dispatch()
local i = 1
local connections = {}
while true do
if threads[i] == nil then -- 还有线程吗
if threads[1] == nil then break end -- 列表是否为空?
i = 1 -- 重新开始循环
connections = {}
end
local status,res = coroutine.resume(threads[i])
if not res then -- 线程是否已经完成了任务?
table.remove(threads, i) -- 移除线程
else
i = i + 1
connections[#connections + 1] = res
if #connections == #threads then -- 所有线程都阻塞了吗?
socket.select(connections)
end
end
end
end
函数get确保每一个下载任务都在一个地理的线程中执行。调度程序本身主要就是一个循环,它遍历所有的线程,逐个唤醒它们的执行。并且当线程完成任务时,将该线程从列表中删除。在所有线程都完成运行后,停止循环。
4、主程序
-- 主程序
host = "www.csdn.net"
get(host, "/")
get(host, "/nav/ai")
get(host, "/nav/news")
get(host, "/nav/cloud")
dispatch() -- 主循环