在Python开发中,线程池(ThreadPool)是一种非常实用的并发编程工具。它通过预先创建一组线程并重复利用它们来执行任务,避免了频繁创建和销毁线程的开销。对于需要处理大量I/O密集型任务的场景,线程池可以显著提升程序性能。
线程池相比直接创建线程有几个明显优势:
让我们从一个最简单的线程池示例开始:
python复制import concurrent.futures
def square(n):
return n * n
def main():
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
future1 = executor.submit(square, 5)
future2 = executor.submit(square, 8)
future3 = executor.submit(square, 3)
print(f"5的平方: {future1.result()}")
print(f"8的平方: {future2.result()}")
print(f"3的平方: {future3.result()}")
if __name__ == "__main__":
main()
这个例子展示了线程池最基本的用法:
注意:result()方法是阻塞调用,会等待任务执行完成才返回结果。如果任务尚未完成,调用result()的线程会被阻塞。
当需要处理多个相似任务时,可以使用列表推导式简化代码:
python复制import concurrent.futures
def square(n):
return n * n
def main():
numbers = [5, 8, 3, 2, 7, 4]
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
futures = [executor.submit(square, n) for n in numbers]
results = [f.result() for f in futures]
print(f"所有结果: {results}")
if __name__ == "__main__":
main()
这种写法的优势在于:
ThreadPoolExecutor还提供了更便捷的map方法:
python复制import concurrent.futures
def square(n):
return n * n
def main():
numbers = [5, 8, 3, 2, 7, 4]
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
results = list(executor.map(square, numbers))
print(f"所有结果: {results}")
if __name__ == "__main__":
main()
map方法与内置map函数类似,但会自动将任务分配给线程池中的工作线程执行。它的特点包括:
提示:executor.map()返回的是一个生成器,如果需要立即获取所有结果,可以像示例中那样转换为list。
max_workers参数决定了线程池中最大工作线程数,设置不当会影响性能:
获取CPU核心数的Python代码:
python复制import os
print(os.cpu_count()) # 输出CPU核心数
线程池任务中的异常不会自动传播到主线程,需要特殊处理:
python复制import concurrent.futures
def may_fail(n):
if n == 0:
raise ValueError("0 is not allowed")
return 100 / n
def main():
numbers = [1, 2, 0, 4]
with concurrent.futures.ThreadPoolExecutor() as executor:
futures = [executor.submit(may_fail, n) for n in numbers]
for future in concurrent.futures.as_completed(futures):
try:
result = future.result()
print(f"结果: {result}")
except Exception as e:
print(f"任务失败: {e}")
if __name__ == "__main__":
main()
as_completed()返回一个迭代器,在任务完成时(无论成功或失败)产出对应的Future对象。通过捕获result()方法可能抛出的异常,可以实现完善的错误处理。
对于可能长时间运行的任务,可以设置超时限制:
python复制import concurrent.futures
import time
def long_task(n):
time.sleep(n)
return n * n
def main():
with concurrent.futures.ThreadPoolExecutor() as executor:
future = executor.submit(long_task, 5)
try:
result = future.result(timeout=3) # 设置3秒超时
print(f"结果: {result}")
except concurrent.futures.TimeoutError:
print("任务执行超时")
if __name__ == "__main__":
main()
为了展示线程池的性能优势,我们对比三种实现方式:
测试代码:
python复制import time
import threading
import concurrent.futures
def task(n):
time.sleep(1) # 模拟I/O操作
return n * n
# 顺序执行
def sequential():
start = time.time()
results = [task(n) for n in range(10)]
print(f"顺序执行耗时: {time.time() - start:.2f}s")
# 每次创建新线程
def new_thread():
start = time.time()
threads = []
results = [None] * 10
for i in range(10):
t = threading.Thread(target=lambda i: results.__setitem__(i, task(i)), args=(i,))
threads.append(t)
t.start()
for t in threads:
t.join()
print(f"新建线程耗时: {time.time() - start:.2f}s")
# 使用线程池
def thread_pool():
start = time.time()
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
results = list(executor.map(task, range(10)))
print(f"线程池耗时: {time.time() - start:.2f}s")
if __name__ == "__main__":
sequential()
new_thread()
thread_pool()
典型输出结果:
code复制顺序执行耗时: 10.02s
新建线程耗时: 1.01s
线程池耗时: 2.01s
可以看到线程池在性能和资源消耗之间取得了良好平衡。
问题1:线程池任务卡死
可能原因:
解决方案:
问题2:内存泄漏
可能原因:
解决方案:
问题3:任务执行顺序不确定
当使用submit+as_completed时,任务完成顺序可能与提交顺序不同。如果需要保持顺序:
python复制futures = [executor.submit(task, n) for n in numbers]
results = [f.result() for f in futures] # 按提交顺序获取结果
Python的全局解释器锁(GIL)会影响多线程程序的性能,但线程池在以下场景仍然有效:
对于纯CPU密集型任务,建议考虑multiprocessing模块的进程池。