多线程 threading
约 1781 字大约 6 分钟
2026-05-10
线程是进程内部的执行流。一个 Python 程序运行时至少有一个主线程,我们可以创建额外线程,让多个任务在同一进程内并发执行。
- 创建线程
- 给线程传参
- 启动多个线程
- 线程名称
- 1对比同步 vs 多线程下载三个 URL:纯顺序 3 秒,线程并发约 1 秒 —— 这是线程的真实价值。
- 2记住两个坑:Thread(target=fn) 不是 fn();args=(x,) 单元素元组逗号别漏。
- 3daemon=True 让线程在主进程退出时跟着关 —— 适合心跳/上报,绝对不能用在“必须保存数据”的任务上。
- 4线程函数的返回值不能直接拿;要么共享变量 + 锁,要么 Queue,要么直接换成 ThreadPoolExecutor。
- 5线程异常默认不会冒到主线程 —— 调试时打日志或换 Executor 拿 Future.exception()。
线程是进程内部的执行流。一个 Python 程序运行时至少有一个主线程,我们可以创建额外线程,让多个任务在同一进程内并发执行。
多线程最适合 IO 密集型任务,例如网络请求、文件读写、数据库访问等。
创建线程
Python 标准库提供了 threading 模块。
import threading
def worker():
print('子线程开始执行')
thread = threading.Thread(target=worker)
thread.start()
print('主线程继续执行')
thread.join()
print('子线程结束')Thread(target=worker) 创建线程对象,start() 启动线程,join() 等待线程结束。
给线程传参
可以通过 args 或 kwargs 给线程函数传参。
import threading
def download(name, seconds):
print(f'开始下载 {name},预计 {seconds} 秒')
thread = threading.Thread(target=download, args=('file.zip', 3))
thread.start()
thread.join()args 必须是元组,即使只有一个参数,也要写成 args=('file.zip',)。
启动多个线程
import threading
import time
def download(url):
print(f'开始下载:{url}')
time.sleep(1)
print(f'下载完成:{url}')
urls = ['a.com', 'b.com', 'c.com']
threads = []
for url in urls:
thread = threading.Thread(target=download, args=(url,))
thread.start()
threads.append(thread)
for thread in threads:
thread.join()
print('全部下载完成')如果顺序执行三个下载,每个 1 秒,总共大约 3 秒。使用线程后,三个任务可以并发等待,总时间接近 1 秒。
线程名称
可以给线程设置名称,方便调试日志。
import threading
def worker():
current = threading.current_thread()
print(f'当前线程:{current.name}')
thread = threading.Thread(target=worker, name='download-worker')
thread.start()
thread.join()daemon 线程
daemon 线程也叫守护线程。当主线程结束时,daemon 线程不会阻止程序退出。
import threading
import time
def background_task():
while True:
print('后台任务运行中')
time.sleep(1)
thread = threading.Thread(target=background_task, daemon=True)
thread.start()
time.sleep(3)
print('主线程结束')daemon 线程适合日志上报、心跳、后台清理等“不需要保证一定执行完”的任务。
如果任务必须完整执行,例如保存文件、写数据库,就不要使用 daemon 线程。
线程的返回值
threading.Thread 本身不会直接返回函数结果。常见做法是使用共享容器或队列。
import threading
results = []
def square(number):
results.append(number * number)
threads = []
for number in [1, 2, 3]:
thread = threading.Thread(target=square, args=(number,))
thread.start()
threads.append(thread)
for thread in threads:
thread.join()
print(results)不过,共享列表在复杂场景下可能有线程安全问题。更推荐使用 queue.Queue 或 concurrent.futures.ThreadPoolExecutor。
多线程适合 IO 密集型任务
线程在等待 IO 时会释放 CPU,让其他线程继续执行。因此对于 IO 密集型任务,多线程通常能显著提升吞吐量。
典型场景:
- 同时请求多个网页
- 同时读取多个文件
- 后台执行耗时 IO
- 多个客户端连接
多线程不适合 CPU 密集型任务
在 CPython 中,因为 GIL 的存在,同一时刻通常只有一个线程能执行 Python 字节码。因此多线程无法让纯 Python CPU 密集型任务真正利用多个 CPU 核心。
例如大量计算、图片处理、压缩等任务,通常应该使用多进程。
注意事项
- 启动线程后,如果需要等待结果,要调用
join()。 - 不要无限制创建线程,线程太多会带来调度和内存开销。
- 多个线程访问共享数据时,要考虑线程安全。
- daemon 线程可能在程序退出时被直接终止。
- 简单批量任务优先考虑线程池。
什么时候适合用线程
线程最适合处理“经常等待”的任务,例如:
- 同时下载多个网页;
- 同时请求多个接口;
- 后台写日志或处理文件;
- GUI 程序里把耗时操作放到后台,避免界面卡死。
如果任务主要是数学计算、图片处理、压缩大量数据,线程未必会明显变快。这类任务更适合看多进程。
用 join 等线程结束
很多初学者会忘记 join()。如果主线程不等待子线程,程序可能还没等任务完成就继续往下走。
import threading
import time
def worker(name):
time.sleep(1)
print(f'{name} 完成')
threads = []
for i in range(3):
t = threading.Thread(target=worker, args=(f'任务{i}',))
t.start()
threads.append(t)
print('所有线程已启动')
for t in threads:
t.join()
print('所有线程都结束了')start() 表示“开始跑”,join() 表示“我在这里等你跑完”。
线程里的异常不会自动抛回主线程
线程函数里如果报错,主线程不一定会像普通函数调用那样直接捕获到。比如:
import threading
def worker():
raise RuntimeError('线程里出错了')
t = threading.Thread(target=worker)
t.start()
t.join()
print('主线程继续执行')你会在控制台看到错误,但主线程可能仍然继续执行。真实项目中,如果需要收集结果和异常,更推荐使用 concurrent.futures.ThreadPoolExecutor,它的 Future 更容易拿到返回值和异常。
线程数量不是越多越好
创建线程也有成本。线程太多会导致:
- 上下文切换变多;
- 内存占用增加;
- 对外部服务请求过猛;
- 日志和错误更难看清。
如果只是练习,开几个线程没问题。如果要处理几十、几百个任务,优先使用线程池,并限制 max_workers。
一个常见的小误区
不要把函数调用结果传给 target:
# 错误:worker() 会立刻在主线程执行
threading.Thread(target=worker())
# 正确:把函数对象交给线程
threading.Thread(target=worker)如果要传参数,用 args:
threading.Thread(target=worker, args=('Alice',))注意单个参数的元组要写成 ('Alice',),逗号不能少。
总结
threading 是 Python 中最基础的多线程工具。它适合处理简单 IO 并发,但一旦涉及大量任务、返回值、异常处理和并发数量控制,通常使用线程池会更方便。
- 线程适合 IO 等待型任务,例如请求接口、下载文件、后台处理。
- `start()` 是启动线程,`join()` 是等待线程结束;不要把函数调用结果传给 target。
- 线程里的返回值和异常不好直接管理,任务多时优先考虑 ThreadPoolExecutor。
版权所有
版权归属:Shuo Liu
