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 密集场景用好多线程可显著提升吞吐。
笔记加载中…