1. 程式人生 > >協程實現併發下載

協程實現併發下載

    在單執行緒的程式中,採取的是順序執行方式。對於下載程式來說,單執行緒的效率是極其低的,原因是它只能在下載完一個檔案後才可以讀取該檔案。當接收一個遠端檔案時,程式將大部分時間花費在等待資料接收上。更明確地說,將時間用在了對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()	-- 主迴圈