对于更复杂的线程逻辑,可以通过继承 Thread 类并重写 run 方法来实现。这种方式将线程代码封装在类中,便于组织和管理复杂的线程行为。
import threading
import random
classDownloadTask(threading.Thread):
def__init__(self, file_id):
super().__init__()
self.file_id = file_id
self.progress = 0defrun(self):
print(f"[{self.name}] 开始下载文件 #{self.file_id}")
for i inrange(100, step=10):
time.sleep(random.uniform(0.1, 0.3))
self.progress = i
self.progress = 100print(f"[{self.name}] 下载完成")
tasks = [DownloadTask(i) for i inrange(3)]
for t in tasks:
t.start()
for t in tasks:
t.join()
1.3 name 与 daemon 参数详解
name 参数为线程设置一个可读的名称,便于调试和日志记录。默认情况下,Python 会为线程自动生成 Thread-1、Thread-2 等名称。通过 threading.current_thread().name 可以在运行时获取当前线程的名称。
import threading
import time
defcompute(n):
print(f"计算 {n} 的平方...")
time.sleep(2)
return n * n
threads = []
results = []
for i inrange(5):
t = threading.Thread(target=lambda x=i: results.append(compute(x)), name=f"Calc-{i}")
threads.append(t)
for t in threads:
t.start()
print(f"{t.name} 是否存活: {t.is_alive()}")
for t in threads:
t.join(timeout=3.0) # 最多等待3秒print(f"{t.name} 已结束: {not t.is_alive()}")
print(f"计算结果: {results}")
import threading
counter = 0
lock = threading.Lock()
ITERATIONS = 100000defsafe_increment():
global counter
for _ inrange(ITERATIONS):
with lock: # 推荐使用上下文管理器,自动 acquire/release
counter += 1
threads = [threading.Thread(target=safe_increment) for _ inrange(10)]
for t in threads: t.start()
for t in threads: t.join()
print(f"加锁后结果: {counter}") # 准确输出 1000000
from concurrent.futures import ThreadPoolExecutor, as_completed
import time
import random
defdownload_url(url):
print(f"开始下载: {url}")
time.sleep(random.uniform(0.5, 2.0))
size = random.randint(1024, 99999)
print(f"完成下载: {url} ({size} bytes)")
return (url, size)
urls = [
"https://example.com/file1.zip",
"https://example.com/file2.zip",
"https://example.com/file3.zip",
"https://example.com/file4.zip",
"https://example.com/file5.zip",
"https://example.com/file6.zip",
"https://example.com/file7.zip",
"https://example.com/file8.zip",
]
# max_workers=4 表示最多同时运行4个线程with ThreadPoolExecutor(max_workers=4) as executor:
# submit 返回 Future 对象
futures = {executor.submit(download_url, url): url for url in urls}
# as_completed 在 Future 完成时立即返回for future inas_completed(futures):
url, size = future.result() # 获取返回值print(f"结果: {url} -> {size} bytes")
print("所有下载任务完成")
# 另一种方式:使用 map,按输入顺序返回结果with ThreadPoolExecutor(max_workers=4) as executor:
results = executor.map(download_url, urls)
for result in results:
print(f"有序结果: {result}")
Executor 最佳实践:使用 with 语句管理线程池生命周期,自动完成清理。submit() 返回 Future 对象,支持回调、取消和超时;map() 按输入顺序返回结果,更简洁但缺乏灵活性。max_workers 一般设置为 CPU 核心数的 5-10 倍(对于 IO 密集型任务)。
七、GIL 深度分析与影响
GIL(Global Interpreter Lock,全局解释器锁)是 CPython 解释器的一个核心设计决策,也是 Python 多线程编程中最重要的性能影响因素。理解 GIL 是写出高效并发代码的前提。
7.1 GIL 是什么
GIL 是一个互斥锁,它保证在任何时刻,同一个进程中只有一个线程在执行 Python 字节码。这意味着即使在多核 CPU 上,Python 多线程也无法真正并行执行计算密集型任务。GIL 的存在简化了 CPython 的内存管理(特别是引用计数),但也牺牲了多线程的并行计算能力。
7.2 IO 密集 vs CPU 密集实测对比
下面的对比实验清晰地展示了 GIL 对不同类型任务的影响:对于 IO 密集型任务,多线程可以显著提升性能(因为线程在等待 IO 时释放 GIL);对于 CPU 密集型任务,多线程由于 GIL 竞争反而可能比单线程更慢。
IO 密集型任务 —— 多线程大幅提升
import threading
import time
defio_task(n):
"""模拟 IO 操作(网络请求、文件读写等)"""for _ inrange(n):
time.sleep(0.01) # 模拟 IO 等待# 单线程
start = time.time()
io_task(200)
print(f"单线程耗时: {time.time() - start:.2f}s")
# 多线程
start = time.time()
threads = [threading.Thread(target=io_task, args=(50,))
for _ inrange(4)]
for t in threads: t.start()
for t in threads: t.join()
print(f"4线程耗时: {time.time() - start:.2f}s")
# IO 密集:多线程快 3-4 倍
CPU 密集型任务 —— 多线程无提升甚至更慢
import threading
import time
defcpu_task(n):
"""模拟 CPU 密集计算"""
result = 0for i inrange(n * 1000000):
result += i ** 2# 单线程
start = time.time()
cpu_task(10)
print(f"单线程耗时: {time.time() - start:.2f}s")
# 多线程
start = time.time()
threads = [threading.Thread(target=cpu_task, args=(2,))
for _ inrange(5)]
for t in threads: t.start()
for t in threads: t.join()
print(f"5线程耗时: {time.time() - start:.2f}s")
# CPU 密集:多线程反而更慢(GIL 切换开销)
7.3 GIL 的应对策略
场景
推荐方案
说明
IO 密集型
多线程 (threading)
GIL 在 IO 等待时释放,多线程有效
CPU 密集型
多进程 (multiprocessing)
每个进程有独立 GIL,可真正并行
混合型
线程 + 进程组合
线程处理 IO,进程处理计算
CPU 密集型(轻量)
协程 (asyncio)
单线程内协作式调度,无 GIL 问题
极致性能需求
C 扩展 / 释放 GIL
C 代码可以显式释放 GIL
GIL 的核心启示:Python 多线程并非"无用",而是需要根据任务类型选择合适的并发模型。对于 IO 密集型任务(网络爬虫、Web 服务器、文件处理),多线程是非常有效的;对于 CPU 密集型任务(图像处理、科学计算),应使用 multiprocessing 或 asyncio。Python 3.13 引入了"自由线程"(free-threaded)模式作为实验特性,未来有望逐步解决 GIL 的限制。