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