You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Python 3中创建启动任意数量线程?多线程图片下载报错求助

嘿,我来帮你理清楚这个问题哈!

首先,你遇到的TypeError: downloading() takes 1 positional arguments but 21 were given,核心原因是线程参数传递的语法坑:

当你用Thread(target=downloading, args=(queue))时,Python会把queue这个集合当成可迭代对象直接解包——因为集合里有21个URL,相当于给downloading函数传了21个参数,但你的函数只定义了1个参数,所以直接报错了。

正确的单参数传递方式是给元组加个逗号:args=(queue,),这样Python才会把整个集合作为单个参数传给函数,而不是拆成一堆参数。

不过,就算你修复了这个语法问题,当前的代码还有两个大问题:线程安全风险和效率低下,我来给你优化一下:

为啥你的代码还有坑?

  1. 线程不安全:全局变量count和集合done在多线程同时读写时,会出现数据竞争——比如两个线程同时检查同一个URL是否在done里,结果都认为不在,导致重复下载;计数count也会因为同时修改出现错误。
  2. 效率低:每个线程都要遍历整个21个URL的集合,大部分时间都在做“是否已下载”的检查,完全是浪费资源。

优化后的靠谱版本

用Python标准库的queue.Queue来做任务队列(天生线程安全),再用Lock保证计数的安全性,代码更简洁也更靠谱:

import urllib.request
from threading import Thread, Lock
from queue import Queue

# 线程安全的计数器和锁
download_count = 0
count_lock = Lock()
save_path = 'foldername'

def download_worker(task_queue, thread_name):
    global download_count
    while not task_queue.empty():
        img_url = task_queue.get()  # 从队列取任务,空队列时自动阻塞
        try:
            # 提取文件名
            filename = img_url.split('/')[-1]
            urllib.request.urlretrieve(img_url, f"{save_path}/{filename}")
            
            # 安全更新下载计数
            with count_lock:
                download_count += 1
                print(f"{download_count} {thread_name}")
                print(f"Done: {filename}")
        except Exception as e:
            print(f"{thread_name} 下载失败 {img_url}: {str(e)}")
        finally:
            task_queue.task_done()  # 标记当前任务完成

def start_multi_thread_download(urls, thread_count=10):
    print('开始下载...')
    task_queue = Queue()
    
    # 把所有URL塞进任务队列
    for url in urls:
        task_queue.put(url)
    
    # 创建并启动线程
    threads = []
    for i in range(thread_count):
        thread = Thread(target=download_worker, args=(task_queue, f"线程 {i+1}"))
        thread.daemon = True  # 设置为守护线程,主程序结束时自动退出
        thread.start()
        threads.append(thread)
    
    # 等待所有任务完成
    task_queue.join()
    print("所有下载任务完成!")

# 调用示例
if __name__ == "__main__":
    # 替换成你的21个URL集合
    image_urls = {"url1", "url2", "url3", "..."}
    start_multi_thread_download(image_urls)

优化点说明:

  • queue.Queue管理任务:每个线程只从队列里拿未处理的任务,不会重复下载,也不用遍历整个集合,效率高多了,而且队列本身就是线程安全的,不用手动加锁判断。
  • 线程安全计数:用Lock包裹计数更新,确保同一时间只有一个线程修改计数,不会出现计数错误。
  • 异常捕获:单个URL下载失败不会导致整个线程崩溃,还能打印错误信息方便排查。
  • 守护线程:主程序完成所有任务后,线程会自动退出,不用手动挨个join线程。
  • 简洁的线程创建:用循环创建指定数量的线程,不用写10遍重复代码。

如果你非要用原来的集合方式(不推荐)

那除了把args=(queue)改成args=(queue,),还要给done集合和count加锁,避免线程安全问题:

import urllib.request
from threading import Thread, Lock

count = 0
count_lock = Lock()
done = set()
done_lock = Lock()
path = 'foldername'

def downloading(queue, name):
    global count
    for imgs in queue:
        with done_lock:
            if imgs in done:
                continue
            done.add(imgs)
        try:
            urllib.request.urlretrieve(imgs, path + '/' + imgs.split('/')[-1])
            with count_lock:
                count += 1
                print(str(count) + ' ' + name)
                print('Done: ' + imgs.split('/')[-1])
        except Exception as e:
            print(f"{name} 下载失败 {imgs}: {str(e)}")

def threads(queue):
    print('Start Downloading ...')
    thread_list = []
    for i in range(10):
        th = Thread(target=downloading, args=(queue, f"Thread {i+1}"))
        th.start()
        thread_list.append(th)
    # 等待所有线程结束
    for th in thread_list:
        th.join()
    print("All done!")

# 调用
queue = {"some urls"}
threads(queue)

但这种方式真的不如用队列的方案高效,还是推荐用第一个优化版本~

内容的提问来源于stack exchange,提问作者Albaraa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 09:06:23