Python3 多线程 threading
程序默认只有主线程一条执行流,遇到"同时下载多个文件、同时请求多个接口"就会排队等待。threading 模块可以创建多条线程并行做事,让程序在等待 IO 时继续干别的活,是 IO 密集型任务提速的常用手段。
线程与 GIL
线程是操作系统调度的最小执行单元,同一进程内的线程共享内存。注意 CPython 的 GIL(全局解释器锁):同一时刻只有一个线程执行 Python 字节码,所以计算密集任务多线程提速有限;但线程等待 IO(网络、磁盘)时会释放 GIL,因此 IO 密集任务收益明显。
创建线程:Thread 与 join
import threading
import time
def download(name):
time.sleep(1) # 模拟下载耗时
print(name, "下载完成")
t = threading.Thread(target=download, args=("第1个文件",))
t.start() # 线程开始运行
print("主线程继续干别的")
t.join() # 阻塞等待 t 结束
# 可能输出:主线程继续干别的 → 第1个文件 下载完成
target 指定线程执行的函数,args 传位置参数(关键字参数用 kwargs)。设置 daemon=True 可创建守护线程——它随主线程退出而终止,适合心跳、监控等后台任务。join() 等待线程结束再继续。
线程安全与 Lock
多线程同时修改共享变量时,"读-改-写"并非原子操作,会互相干扰。例如 10 个线程各给 balance 加 1000 次,不加锁结果可能小于 10000。用 Lock(互斥锁) 保护临界区:
import threading
balance = 0
lock = threading.Lock()
def deposit(n):
global balance
for _ in range(1000):
lock.acquire() # 加锁:同一时刻只有一个线程能进入
balance += n # 临界区:读-改-写一气呵成
lock.release() # 释放锁
ts = [threading.Thread(target=deposit, args=(1,)) for _ in range(10)]
for t in ts:
t.start()
for t in ts:
t.join()
print(balance) # 输出:10000(不加锁时往往小于 10000)
更稳妥的写法是用 with lock:,自动加锁并在任何异常下都能释放:
with lock:
balance += n # 等价于 acquire + try/finally + release
生产者-消费者示例
线程间传数据优先用 queue.Queue(自带锁、线程安全):生产者线程放,消费者线程取,队列空时 get 自动阻塞等待:
import queue
import threading
import time
q = queue.Queue()
def producer():
for i in range(5):
q.put(i) # 生产数据
time.sleep(0.1)
def consumer():
while True:
item = q.get() # 队列空时阻塞等待
print("消费", item)
q.task_done() # 通知队列本任务完成
threading.Thread(target=producer).start()
threading.Thread(target=consumer, daemon=True).start()
q.join() # 等待全部任务处理完
print("消费完毕") # 输出:消费完毕
线程池 ThreadPoolExecutor
任务一多,手动管理线程既麻烦又浪费,concurrent.futures 的线程池可复用线程并收集结果:
from concurrent.futures import ThreadPoolExecutor
def fetch(url):
return f"{url} 抓取完成"
with ThreadPoolExecutor(max_workers=4) as pool: # 最多 4 个线程并行
results = list(pool.map(fetch, ["a.com", "b.com", "c.com"]))
print(results) # 输出:['a.com 抓取完成', 'b.com 抓取完成', 'c.com 抓取完成']
适用场景
- IO 密集(请求接口、读写数据库/文件、爬虫)→ 多线程能明显提速;
- 计算密集 → 建议改用 multiprocessing 多进程,绕开 GIL;
- 共享数据一律加 Lock;线程间传数据优先用 queue.Queue,不要裸写全局变量。 小结:Thread 开线程、start/join 管生命周期,Lock 保证线程安全,queue 完成线程间通信,ThreadPoolExecutor 简化批量任务——IO 密集场景用好多线程可显著提升吞吐。