![](/img/trans.png)
[英]Python speed optimization when downloading 1000+ zip files from URL
[英]Downloading over 1000 files in python
因此,也許從我的代碼開始:
def download(fn, filename, index):
urllib.request.urlretrieve(fn,
os.path.join('music', re.sub('[%s]' % ''.join(CHAR_NOTALLOWED), '', filename) + '.mp3'))
print(str(index) + '# DOWNLOADED: ' + filename)
和
for index, d in enumerate(found):
worker = Thread(target=download, args=(found[d], d, index))
worker.setDaemon(True)
worker.start()
worker.join()
我的問題是,當我嘗試下載1000個以上的文件時,總是會收到此錯誤,但我不知道為什么:
Traceback (most recent call last):
File "E:/PythonProject/1.1/mp3y.py", line 238, in <module>
worker.start()
File "E:\python34\lib\threading.py", line 851, in start
_start_new_thread(self._bootstrap, ())
RuntimeError: can't start new thread
我嘗試使用隊列,但遇到了相同的錯誤....我想將此線程分為一部分,但我不知道如何:O
簡潔版本:
with concurrent.futures.ThreadPoolExecutor(max_workers=12) as executor:
for index, d in enumerate(found):
executor.submit(download, found[d], d, index)
而已; 一次微不足道的更改,並且比現有代碼少兩行,您就完成了。
那么,您現有的代碼有什么問題? 一次啟動1000個線程始終是一個壞主意。*一旦超過幾十個線程,您將增加的調度程序和上下文切換開銷要比並發節省更多。
如果您想知道為什么它在1000左右失敗,那可能是因為一個庫在舊版本的Windows上運行,**,或者是因為您的堆棧空間不足了,***。 但是無論哪種方式,都沒有關系。 正確的解決方案是不要使用太多線程。
通常的解決方案是使用線程池-啟動大約8-12個線程,****並讓它們提取URL以從隊列中下載。 你可以建立這個自己,或者你可以使用concurrent.futures.ThreadPoolExecutor
或multiprocessing.dummy.Pool
附帶STDLIB。 如果您查看文檔中的主要ThreadPoolExecutor
示例 ,它幾乎可以完全滿足您的要求。 實際上,您想要的甚至更簡單,因為您不在乎結果。
附帶說明一下,您的代碼中還有另一個嚴重的問題。 如果守護線程,則不允許join
它們。 另外,您僅嘗試加入創建的最后一個,但絕不能保證最后一個完成。 同樣,首先要守護下載線程可能不是一個好主意,因為當您的主線程完成時(在等待一個任意選擇的下載完成之后),其他線程可能會被打斷並留下部分文件。
另外,如果你想守護進程線程,最好的辦法是通過daemon=True
給構造函數。 如果您需要在創建后執行此操作,只需執行t.daemon = True
。 僅在需要向后兼容Python 2.5時才調用不setDaemon
使用的setDaemon
函數。
*我想我不應該總是這樣說,因為在2025年,要利用您成千上萬個慢速內核,這將是日常工作。 但是在2014年,在普通筆記本電腦/台式機/服務器硬件上,情況總是很糟糕。
**較舊版本的Windows(至少NT 4)在接近1024個線程時會出現各種奇怪的錯誤,因此許多線程庫僅拒絕創建1000個以上的線程。 盡管這里似乎不是這種情況,因為Python只是調用了Microsoft自己的包裝函數_beginthreadex
,但沒有這樣做。
***默認情況下,每個線程獲得1MB的堆棧空間。 並且在32位應用程序中,有最大的總堆棧空間,我假定您的Windows版本默認為1GB。 您可以為每個線程自定義堆棧空間,也可以自定義總進程堆棧空間,但是Python既不自定義,也幾乎不自定義任何其他應用程序。
****除非您的下載全部來自同一服務器,否則這種情況下,您最多可能需要4個,如果不是您的服務器,則實際上多於2個通常被認為是不禮貌的。 為何還要8-12? 憑經驗證明,很久以前就進行了很好的測試。 它可能不再是最佳的,但對於大多數用途而言,它可能已經足夠接近了。 如果您確實需要提高性能,可以使用不同的數字進行測試。
通常,所允許的最大線程數是有限制的。 根據您的系統,這個數目可能在幾十到數千之間,但是考慮到您打算下載的文件數量,不要指望可以創建相同數量的線程。
通常不要同時啟動1000個以上的線程,每次嘗試下載一個文件時都不是一個好主意。 您的連接將立即阻塞,這比一次下載幾個文件的效率低得多,此外,它浪費了很多服務器資源,因此被認為不易社交。
在一個情況下使用這樣的模式來創建一個小的工作線程 ,每個投票方式的queue.Queue
對於要下載的文件,然后下載一個文件,然后輪詢下一個文件的隊列。 現在,主程序可以從原始列表中饋入該隊列,調度文件下載,直到所有下載完成。
此規則的一個明顯例外是,如果您是從人為地限制下載速度的站點下載文件。 尤其是視頻門戶網站因此而聞名。 在這種情況下,使用大量線程可能是適當的。 在一種情況下,從dailymotion下載時,我發現許多20–30個線程最適合我。
使用隊列可以工作,但是您必須限制創建的輔助線程的數量。 這是使用100個工作人員和一個Queue
來處理1000個工作項目的代碼:
import Queue
from threading import Thread
def main():
nworkers = 100
q = Queue.Queue(1000+nworkers)
# add the work
for i in range(1000):
q.put(i)
# add the stop signals
for i in range(nworkers):
q.put(-1)
# create and start up the threads
workers = []
for wid in range(nworkers):
w = Thread(target = dowork, args = (q, wid))
w.start()
workers.append(w)
# join all of the workers
for w in workers: w.join()
print "All done!"
def dowork(q, wid):
while True:
j = q.get()
if j < 0:
break
else:
print "Worker", wid, "processing item", j
print "Worker", wid, "exiting"
if __name__ == "__main__":
main()
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.