所以,也许从我的代码开始:
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
答案 0 :(得分:4)
简短版本:
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以从队列中下载。您可以自己构建,也可以使用stdlib附带的concurrent.futures.ThreadPoolExecutor
或multiprocessing.dummy.Pool
。如果您查看文档中的主ThreadPoolExecutor
Example,它几乎完全符合您的要求。事实上,你想要的更简单,因为你不关心结果。
作为旁注,您的代码中还有另一个严重问题。如果您对线程进行守护,则不允许join
它们。此外,您只是尝试加入您创建的最后一个,这绝不是最后一个完成。另外,守护进程下载线程可能是一个坏主意,因为当你的主线程完成时(在等待一个任意选择的下载完成之后),其他线程可能会被中断并留下部分文件。
此外,如果你做想要守护一个线程,最好的方法是将daemon=True
传递给构造函数。如果您需要在创建后执行此操作,只需执行t.daemon = True
。如果您需要向后兼容Python 2.5,请仅调用已弃用的setDaemon
函数。
*我想我不应该说总是,因为在2025年它可能是日常事情,要利用你的数千个慢速核心。但在2014年普通的笔记本电脑/台式机/服务器硬件上,它总是很糟糕。
**当你接近1024个线程时,旧版本的Windows(至少是NT 4)有各种奇怪的错误,因此许多线程库只是拒绝创建超过1000个线程。虽然这似乎不是这种情况,如Python is just calling Microsoft's own wrapper function _beginthreadex
,但不这样做。
***默认情况下,每个线程获得1MB的堆栈空间。在32位应用程序中,有一个最大的总堆栈空间,我假设您的Windows版本默认为1GB。您可以为每个线程或整个进程堆栈空间自定义堆栈空间,但Python不会自定义,也不会自定义任何其他应用程序。
****除非您的下载全部来自同一台服务器,在这种情况下您最多可能需要4个,如果不是您的服务器,通常认为超过2个通常被认为是不礼貌的。为什么8-12呢?很久以前,这是一个经验丰富的经验法则。它可能不再是最优的,但它可能足够接近大多数用途。如果你真的需要挤出更多的性能,你可以测试不同的数字。
答案 1 :(得分:3)
允许的最大线程数通常有限制。根据您的系统,这可能是几十到几千,但考虑到您打算下载的文件数量,不要指望您可以创建相同数量的线程。
每次尝试下载文件时,同时启动1000多个线程通常不是一个好主意。您的连接会立即堵塞,效率低于一次下载几个文件的效率,除此之外,它浪费了大量的服务器资源,因此不被认为非常善于交际。 / p>
在这种情况下用于创建少量工作线程的模式,每个模式都要queue.Queue
轮询要下载的文件,然后下载文件,然后轮询队列下一个文件。主程序现在可以从原始列表中提供此队列,安排文件下载,直到所有下载完成。
此规则的一个值得注意的例外是,如果您从一个人工限制下载速度的站点下载文件。特别是视频门户网站以此为人所知。在这种情况下,使用明显更多的线程可能是合适的。在一个案例中,当从dailymotion下载时,我发现20-30个线程最适合我。
答案 2 :(得分:0)
使用队列会起作用,但您必须限制您创建的工作线程数。以下是使用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()