协程实现并发下载

    在单线程的程序中,采取的是顺序执行方式。对于下载程序来说,单线程的效率是极其低的,原因是它只能在下载完一个文件后才可以读取该文件。当接收一个远程文件时,程序将大部分时间花费在等待数据接收上。更明确地说,将时间用在了对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()	-- 主循环

猜你喜欢

转载自blog.csdn.net/snailcpp/article/details/79372576