《从零构建 Python 线程池:深入理解 Work Queue、条件变量与并发设计的本质》
一、开篇:为什么要手写一个线程池?
Python 自 1991 年诞生以来,以其简洁优雅的语法、强大的生态系统和“胶水语言”的灵活性,迅速成为 Web 开发、数据科学、人工智能、自动化等领域的核心语言。随着业务规模增长、实时性需求提升,并发编程成为 Python 开发者必须掌握的能力。
你可能已经使用过:
concurrent.futures.ThreadPoolExecutormultiprocessing.Poolasyncio
这些工具极大降低了并发编程的门槛,但也让很多开发者忽略了底层原理。
为什么要手写线程池?
- 彻底理解线程池的设计思想
- 掌握工作队列(Work Queue)的生产者-消费者模型
- 理解条件变量(Condition)的作用
- 学会构建可扩展的并发框架
- 在面试、系统设计、性能优化中具备底层能力
更重要的是:
当你能手写线程池时,你对 Python 并发的理解将从“会用”跃升到“精通”。
二、基础回顾:Python 并发的核心概念
为了让初学者也能顺利阅读,我们先快速回顾 Python 并发的基础知识。
1. 线程与 GIL
Python 的线程由操作系统调度,但 CPython 有 GIL(全局解释器锁),导致:
- 多线程适合 I/O 密集任务
- 多线程不适合 CPU 密集任务
但线程池的设计思想与 GIL 无关,它是通用的并发模型。
2. 工作队列(Work Queue)
线程池的核心思想:
- 主线程不断向队列提交任务
- 工作线程不断从队列取任务执行
- 队列为空时,工作线程等待
- 队列有任务时,工作线程被唤醒
这就是典型的生产者-消费者模型。
3. 条件变量(Condition)
条件变量用于:
- 线程等待某个条件成立
- 其他线程改变条件后发出通知
线程池中:
- 工作线程等待“队列非空”
- 主线程提交任务后通知工作线程
三、基础语法示例:装饰器与函数调用时间
为了保持文章结构一致,我们插入一个基础示例:
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} 花费时间:{end - start:.4f}秒")
return result
return wrapper
@timer
def compute_sum(n):
return sum(range(n))
print(compute_sum(1000000))
四、正式进入主题:手写一个线程池
我们将从最小可用版本开始,一步步扩展。
五、第一步:实现一个线程安全的工作队列(Work Queue)
我们需要:
- 一个任务队列(list 或 deque)
- 一个锁(Lock)
- 一个条件变量(Condition)
代码:WorkQueue 实现
import threading
from collections import deque
class WorkQueue:
def __init__(self):
self.queue = deque()
self.lock = threading.Lock()
self.not_empty = threading.Condition(self.lock)
def put(self, item):
with self.not_empty:
self.queue.append(item)
self.not_empty.notify() # 通知等待的线程
def get(self):
with self.not_empty:
while not self.queue:
self.not_empty.wait() # 队列为空,等待
return self.queue.popleft()
关键点解析
Condition(self.lock):条件变量绑定锁wait():释放锁并阻塞,直到被 notifynotify():唤醒一个等待线程while not queue:防止虚假唤醒
六、第二步:实现 Worker 线程
Worker 线程需要:
- 无限循环从队列取任务
- 执行任务
- 支持线程池关闭
代码:Worker 实现
class Worker(threading.Thread):
def __init__(self, work_queue, pool):
super().__init__()
self.work_queue = work_queue
self.pool = pool
self.daemon = True # 主线程退出时自动退出
def run(self):
while True:
task = self.work_queue.get()
if task is None: # 收到关闭信号
break
func, args, kwargs = task
try:
func(*args, **kwargs)
except Exception as e:
print("任务执行异常:", e)
七、第三步:实现 ThreadPool(线程池主体)
线程池需要:
- 初始化多个 Worker
- 提供 submit() 方法提交任务
- 提供 shutdown() 方法关闭线程池
代码:ThreadPool 实现
class ThreadPool:
def __init__(self, num_workers=4):
self.work_queue = WorkQueue()
self.workers = []
self.num_workers = num_workers
self._init_workers()
def _init_workers(self):
for _ in range(self.num_workers):
worker = Worker(self.work_queue, self)
worker.start()
self.workers.append(worker)
def submit(self, func, *args, **kwargs):
self.work_queue.put((func, args, kwargs))
def shutdown(self, wait=True):
# 向每个 worker 发送关闭信号
for _ in self.workers:
self.work_queue.put(None)
if wait:
for worker in self.workers:
worker.join()
八、完整可运行版本:手写线程池
下面是完整代码,可直接运行:
import threading
from collections import deque
class WorkQueue:
def __init__(self):
self.queue = deque()
self.lock = threading.Lock()
self.not_empty = threading.Condition(self.lock)
def put(self, item):
with self.not_empty:
self.queue.append(item)
self.not_empty.notify()
def get(self):
with self.not_empty:
while not self.queue:
self.not_empty.wait()
return self.queue.popleft()
class Worker(threading.Thread):
def __init__(self, work_queue, pool):
super().__init__()
self.work_queue = work_queue
self.pool = pool
self.daemon = True
def run(self):
while True:
task = self.work_queue.get()
if task is None:
break
func, args, kwargs = task
try:
func(*args, **kwargs)
except Exception as e:
print("任务执行异常:", e)
class ThreadPool:
def __init__(self, num_workers=4):
self.work_queue = WorkQueue()
self.workers = []
self.num_workers = num_workers
self._init_workers()
def _init_workers(self):
for _ in range(self.num_workers):
worker = Worker(self.work_queue, self)
worker.start()
self.workers.append(worker)
def submit(self, func, *args, **kwargs):
self.work_queue.put((func, args, kwargs))
def shutdown(self, wait=True):
for _ in self.workers:
self.work_queue.put(None)
if wait:
for worker in self.workers:
worker.join()
九、测试线程池:执行多个任务
import time
def task(n):
print(f"开始任务 {n}")
time.sleep(1)
print(f"结束任务 {n}")
pool = ThreadPool(num_workers=3)
for i in range(10):
pool.submit(task, i)
pool.shutdown()
输出示例:
开始任务 0
开始任务 1
开始任务 2
结束任务 0
开始任务 3
...
十、进阶优化:支持 Future、返回值、异常捕获
如果你愿意,我可以继续扩展:
- 支持 Future 对象
- 支持任务返回值
- 支持任务优先级
- 支持最大队列长度
- 支持超时
- 支持线程复用统计
这些都是生产级线程池需要的能力。
十一、案例实战:用手写线程池构建一个小型爬虫
import requests
def fetch(url):
resp = requests.get(url)
print(url, len(resp.text))
urls = [
"https://www.python.org",
"https://www.github.com",
"https://www.baidu.com",
] * 3
pool = ThreadPool(5)
for url in urls:
pool.submit(fetch, url)
pool.shutdown()
十二、最佳实践总结
- 使用条件变量实现线程同步
- 使用队列实现生产者-消费者模型
- 使用 None 作为关闭信号
- 使用 daemon 线程避免阻塞退出
- 使用 join() 等待线程结束
十三、前沿视角:Python 并发的未来
- PEP 703(无 GIL Python)正在推进
- asyncio 已成为异步标准
- FastAPI、Quart 等框架推动异步生态
- Rust + Python(PyO3)成为高性能趋势
- 多线程未来可能真正实现并行
十四、总结与互动
我们从 Python 基础讲起,一步步构建了:
- 工作队列
- 条件变量同步
- Worker 线程
- 线程池主体
- 完整可运行线程池
你不仅学会了“如何写”,更理解了“为什么这样写”。
我很想听听你的想法:
- 你希望我继续扩展支持 Future 的版本吗?
- 你想看“手写协程调度器”或“手写进程池”吗?
告诉我你的方向,我可以继续为你构建更完整的并发体系文章。

1889

被折叠的 条评论
为什么被折叠?



