资讯动态

Python并发编程实战:多线程、多进程与线程池进程池应用指南

发布时间:2026/9/2 3:40:42 来源:尧图企业网站定制
这次我们来看一套完整的 Python 并发编程教程。对于任何想要提升程序性能、处理 I/O 密集型任务或构建高响应应用的开发者来说并发编程都是绕不开的核心技能。这套教程从零基础出发覆盖了多线程、多进程、线程同步、进程通信以及 ThreadLocal 等关键概念目标是让你不仅能理解原理更能动手写出稳定高效的并发代码。很多教程要么只讲理论要么例子脱离实际。这套教程的重点是“实战”直接告诉你什么场景下该用什么并发模型如何避免常见的坑以及如何观察和优化并发程序的性能。无论你是想优化爬虫速度、加速数据处理还是构建一个能同时服务多个用户的 Web 应用后端这里的内容都能提供直接的指导。本文将带你快速梳理 Python 并发编程的核心知识体系并通过一系列可运行的代码示例演示从基础概念到实战应用的全过程。我们会重点关注线程与进程的本质区别、GIL全局解释器锁的影响、各种同步原语的使用场景以及如何安全地在进程间传递数据。读完本文你将能清晰地判断何时使用多线程、何时使用多进程并掌握构建健壮并发程序的关键技术。1. 核心能力速览在深入细节之前我们先通过一个表格快速了解 Python 并发编程的核心组件及其适用场景这能帮助你快速建立整体认知。能力项说明典型应用场景多线程 (threading)利用线程实现并发。线程共享同一进程的内存空间创建和切换开销小。受 Python GIL 限制CPU 密集型任务无法真正并行。I/O 密集型任务如网络请求、文件读写、数据库查询、Web 服务器处理请求。多进程 (multiprocessing)利用进程实现并行。每个进程有独立的内存空间和 Python 解释器可绕过 GIL实现多核 CPU 的并行计算。进程间通信IPC开销较大。CPU 密集型任务如科学计算、图像处理、视频编码、大规模数据转换。线程同步协调多个线程对共享资源的访问防止数据竞争和不一致。保护共享变量、数据结构、文件句柄等确保线程安全。进程通信 (IPC)在不同进程间交换数据。由于内存独立需要特定的通信机制。多进程任务中需要传递中间结果或状态信息。ThreadLocal为每个线程提供独立的变量副本避免在函数调用间显式传递线程相关数据。Web 框架中存储当前请求上下文、数据库连接会话等。并发工具 (concurrent.futures)高级抽象接口简化线程池和进程池的使用。快速提交一批任务并异步获取结果代码更简洁。异步 I/O (asyncio)单线程内基于事件循环的协程并发模型高效处理大量 I/O 操作。高性能网络应用、微服务、实时通信。2. 适用场景与使用边界学习并发编程首先要明白“为什么用”和“什么时候用”。盲目使用并发不仅不能提升性能反而可能引入复杂的 Bug 和性能下降。适合使用并发编程的场景I/O 密集型任务程序大部分时间在等待外部响应如从网络下载文件、查询数据库、调用远程 API。使用多线程或asyncio可以在等待一个任务时执行其他任务极大提升吞吐量。CPU 密集型任务程序需要进行大量计算如图像渲染、数据加密解密、复杂数学运算。使用多进程可以充分利用多核 CPU实现真正的并行计算。需要高响应的用户界面在 GUI 应用中使用后台线程执行耗时操作可以防止界面“卡死”保持对用户操作的响应。批量任务处理需要处理大量独立的数据项如批量转换图片格式、清洗日志文件。使用线程池或进程池可以显著缩短总处理时间。Python 并发编程的边界与限制全局解释器锁 (GIL)这是 CPython 解释器的特性。它确保同一时刻只有一个线程执行 Python 字节码。这意味着纯 Python 代码的多线程无法利用多核进行并行计算。GIL 主要影响 CPU 密集型任务对 I/O 密集型任务影响较小因为线程在等待 I/O 时会释放 GIL。复杂度与调试难度并发程序引入了不确定性执行顺序可能每次都不一样导致 Bug 难以复现和调试。死锁、竞态条件等问题需要精心设计才能避免。资源开销线程和进程的创建、销毁、切换都需要消耗系统资源内存、CPU 时间。创建过多并发单元会导致系统负载过重性能反而下降。数据共享与通信多线程间共享数据需要同步否则数据会错乱。多进程间内存不共享通信需要通过队列、管道等机制有额外开销。核心原则如果任务主要是 I/O 等待优先考虑多线程或asyncio如果是纯计算必须使用多进程。在决定引入并发前先评估是否有性能瓶颈并做好测试。3. 环境准备与前置条件Python 并发编程的核心模块是标准库的一部分因此环境准备相对简单。但为了获得更好的开发体验和进行性能测试我们建议准备以下环境。基础环境要求操作系统Windows、macOS 或 Linux 均可。部分进程间通信的底层机制在不同系统上可能有细微差异但multiprocessing模块已做了良好封装。Python 版本推荐使用Python 3.7 及以上版本。本文示例基于 Python 3.8 编写确保能使用concurrent.futures等现代模块的全部功能。你可以通过命令行检查版本python --version # 或 python3 --version代码编辑器或 IDE推荐使用 VSCode、PyCharm 等它们对代码调试、特别是多线程调试有较好的支持。可选工具与库性能分析工具了解cProfile和time模块用于分析程序热点和测量执行时间。系统监控学习使用操作系统的任务管理器、top、htop或ps命令观察程序运行时的 CPU 和内存占用情况特别是多进程时的资源消耗。一个重要的心理准备并发程序的输出顺序可能是不确定的这是正常现象。调试时可以使用日志模块logging并带上线程/进程名这比直接print更清晰。import logging import threading import time logging.basicConfig(levellogging.INFO, format%(asctime)s - %(threadName)s - %(message)s) def worker(): logging.info(Starting work) time.sleep(1) logging.info(Finished work) if __name__ __main__: threads [] for i in range(3): t threading.Thread(targetworker, namefWorker-{i}) threads.append(t) t.start() for t in threads: t.join()运行上述代码观察日志输出中线程名的变化和时间的交错这是理解并发执行的第一步。4. 从基础到实践多线程编程多线程是并发编程中最常用的模型之一。Python 通过threading模块提供线程支持。4.1 创建与启动线程有两种主要方式创建线程实例化Thread类或继承Thread类并重写run方法。方法一传递函数import threading import time def download_file(url): print(f[{threading.current_thread().name}] 开始下载 {url}) time.sleep(2) # 模拟网络延迟 print(f[{threading.current_thread().name}] 下载完成 {url}) if __name__ __main__: print(f[{threading.current_thread().name}] 主线程开始) urls [https://example.com/1.zip, https://example.com/2.mp4, https://example.com/3.pdf] threads [] for url in urls: # 创建线程target 指定要执行的函数args 指定函数参数元组形式 t threading.Thread(targetdownload_file, args(url,), namefDownloader-{urls.index(url)}) threads.append(t) t.start() # 启动线程线程开始执行 target 函数 # 等待所有线程执行完毕 for t in threads: t.join() print(f[{threading.current_thread().name}] 所有下载任务完成)关键点threading.current_thread().name获取当前线程名。t.start()启动线程它是异步的调用后立即返回。t.join()阻塞主线程直到该线程执行完毕。如果不join主线程可能提前结束导致子线程被强制终止。方法二继承 Thread 类import threading import time class DownloadThread(threading.Thread): def __init__(self, url): super().__init__() # 必须调用父类初始化 self.url url def run(self): # 重写 run 方法线程启动后执行此方法 print(f[{self.name}] 开始下载 {self.url}) time.sleep(2) print(f[{self.name}] 下载完成 {self.url}) if __name__ __main__: urls [https://example.com/a.jpg, https://example.com/b.png] threads [DownloadThread(url) for url in urls] for t in threads: t.start() for t in threads: t.join()这种方式更面向对象适合将线程相关的数据和逻辑封装在一起。4.2 线程同步保护共享资源当多个线程同时修改同一个变量或数据结构时就会发生竞态条件导致结果不可预测。我们需要使用“锁”来同步线程。import threading import time # 一个共享的计数器 counter 0 # 创建一个锁对象 lock threading.Lock() def increment_counter(iterations): global counter for _ in range(iterations): # 不安全的操作 # temp counter # time.sleep(0.0001) # 模拟一个极短的切换点 # temp 1 # counter temp # 使用锁保护临界区 with lock: # 自动获取和释放锁 temp counter time.sleep(0.0001) # 即使在这里切换线程锁也能保护数据 temp 1 counter temp # 锁在 with 块结束时自动释放 def unsafe_increment(iterations): global counter for _ in range(iterations): temp counter time.sleep(0.0001) temp 1 counter temp if __name__ __main__: threads [] num_threads 10 iterations_per_thread 100 print( 测试不安全的自增 ) counter 0 for i in range(num_threads): t threading.Thread(targetunsafe_increment, args(iterations_per_thread,)) threads.append(t) t.start() for t in threads: t.join() print(f预期结果: {num_threads * iterations_per_thread}) print(f实际结果: {counter}) # 结果几乎肯定小于预期 print(\n 测试使用锁的自增 ) counter 0 threads.clear() for i in range(num_threads): t threading.Thread(targetincrement_counter, args(iterations_per_thread,)) threads.append(t) t.start() for t in threads: t.join() print(f预期结果: {num_threads * iterations_per_thread}) print(f实际结果: {counter}) # 结果正确运行结果分析第一次无锁的测试最终counter的值会远小于 1000因为多个线程同时读取和写入发生了数据覆盖。第二次使用锁确保了同一时刻只有一个线程执行counter的“读-改-写”操作结果正确。其他同步原语threading.RLock: 可重入锁同一个线程可以多次获取防止死锁在嵌套锁中。threading.Semaphore: 信号量用于控制同时访问资源的线程数量。threading.Event: 事件用于线程间简单的通知机制。threading.Condition: 条件变量用于复杂的线程间协调如生产者-消费者模型。4.3 线程局部数据ThreadLocalThreadLocal数据是线程私有的其他线程无法访问。它解决了参数在函数调用链中层层传递的麻烦常用于存储请求上下文、数据库会话等。import threading import random # 创建一个 ThreadLocal 实例 local_data threading.local() def show_value(): try: value local_data.value print(f[{threading.current_thread().name}] value {value}) except AttributeError: print(f[{threading.current_thread().name}] No value set) def worker(): # 为当前线程设置一个随机值 local_data.value random.randint(1, 100) show_value() if __name__ __main__: show_value() # 主线程没有设置 value会抛出 AttributeError # 为主线程也设置一个值 local_data.value Main Thread Value show_value() print(\n--- 启动子线程 ---) threads [] for i in range(3): t threading.Thread(targetworker, namefWorker-{i}) threads.append(t) t.start() for t in threads: t.join() print(\n--- 再次在主线程中查看 ---) show_value() # 主线程的值依然是 “Main Thread Value”不受子线程影响每个线程操作local_data的属性都像是在操作自己独有的对象互不干扰。这在 Web 框架如 Flask、Django中广泛使用用来存储当前请求的全局信息。5. 突破 GIL多进程编程对于 CPU 密集型任务多线程由于 GIL 的存在无法提速这时就需要使用多进程。multiprocessing模块提供了与threading类似的接口但创建的是进程。5.1 创建进程创建进程的方式与线程非常相似。import multiprocessing import time import os def cpu_bound_task(number): 一个模拟的CPU密集型任务计算平方和 print(f[进程 {os.getpid()}] 开始计算 {number} 的平方和) result sum(i * i for i in range(number)) print(f[进程 {os.getpid()}] 计算完成结果: {result}) return result if __name__ __main__: # 多进程编程必须要有这行 print(f[主进程 {os.getpid()}] 开始) start_time time.time() numbers [5000000, 5000000, 5000000, 5000000] # 四个大数 # 方法1顺序执行 # results [cpu_bound_task(num) for num in numbers] # 方法2使用多进程 processes [] results [] for num in numbers: p multiprocessing.Process(targetcpu_bound_task, args(num,)) processes.append(p) p.start() for p in processes: p.join() # 等待进程结束 elapsed_time time.time() - start_time print(f[主进程 {os.getpid()}] 所有任务完成耗时: {elapsed_time:.2f} 秒)关键点if __name__ __main__:这是多进程编程的强制要求。在 Windows 和 macOS 上Python 会通过 spawn 或 fork 方式创建子进程子进程会导入主模块。如果没有这个保护子进程会无限递归地创建新进程导致错误。os.getpid()获取当前进程的 ID可以看到任务是在不同进程中执行的。性能对比你可以注释掉多进程的部分取消注释顺序执行的部分对比两者的运行时间。在多核 CPU 上多进程版本的时间应该接近顺序执行时间的 1/4假设有4个核心。5.2 进程间通信 (IPC)进程拥有独立的内存空间不能像线程那样直接共享变量。multiprocessing模块提供了多种 IPC 机制如Queue、Pipe、Value、Array以及共享内存等。使用Queue进行通信Queue是进程安全的非常适合生产者-消费者模式。import multiprocessing import time import random def producer(queue, items): 生产者进程向队列中放入数据 for item in items: print(f[生产者 {multiprocessing.current_process().name}] 生产了: {item}) queue.put(item) time.sleep(random.uniform(0.1, 0.5)) # 模拟生产耗时 # 放入结束信号 queue.put(None) print(f[生产者 {multiprocessing.current_process().name}] 生产完毕) def consumer(queue): 消费者进程从队列中取出数据并处理 while True: item queue.get() if item is None: # 收到结束信号 print(f[消费者 {multiprocessing.current_process().name}] 收到结束信号) queue.put(None) # 将信号传递给其他消费者如果有 break print(f[消费者 {multiprocessing.current_process().name}] 消费了: {item}) time.sleep(random.uniform(0.2, 0.8)) # 模拟消费耗时 if __name__ __main__: # 创建一个跨进程的队列 task_queue multiprocessing.Queue(maxsize5) # 设置队列最大容量 # 准备数据 data_to_produce [fTask-{i} for i in range(10)] # 创建进程 prod_process multiprocessing.Process(targetproducer, args(task_queue, data_to_produce), nameProducer-1) cons_process multiprocessing.Process(targetconsumer, args(task_queue,), nameConsumer-1) # 启动进程 cons_process.start() time.sleep(1) # 让消费者先启动并等待 prod_process.start() # 等待进程结束 prod_process.join() cons_process.join() print(主进程结束)Queue内部实现了锁和信号量保证了多进程环境下数据的安全存取。maxsize参数可以控制队列容量当队列满时put操作会阻塞当队列空时get操作会阻塞。使用Pipe进行双向通信Pipe返回一对连接对象默认是全双工的两端都可收发。import multiprocessing def worker(conn): 子进程函数 # 接收来自父进程的消息 received conn.recv() print(f[子进程] 收到: {received}) # 发送回复给父进程 conn.send(f子进程回复: {received.upper()}) conn.close() # 关闭连接 if __name__ __main__: # 创建管道返回两个连接对象 parent_conn, child_conn multiprocessing.Pipe() p multiprocessing.Process(targetworker, args(child_conn,)) p.start() # 父进程发送消息 parent_conn.send(Hello from parent process) # 父进程接收回复 reply parent_conn.recv() print(f[父进程] 收到回复: {reply}) p.join()Pipe适用于两个进程间点对点的通信比Queue更轻量但管理多个连接时不如Queue方便。6. 高级抽象使用线程池与进程池手动管理大量线程或进程的创建和销毁是繁琐且容易出错的。concurrent.futures模块提供了ThreadPoolExecutor和ProcessPoolExecutor这两个高级接口它们管理着一个工作线程或进程的池子我们只需提交任务即可。6.1 使用 ThreadPoolExecutorimport concurrent.futures import urllib.request import time URLS [ https://www.python.org/, https://www.github.com/, https://www.stackoverflow.com/, https://www.google.com/, https://www.bing.com/, ] def fetch_url(url): 获取URL的内容大小模拟I/O密集型任务 start time.time() try: with urllib.request.urlopen(url, timeout5) as conn: data conn.read() size len(data) except Exception as e: return url, fERROR: {e}, time.time() - start return url, f{size} bytes, time.time() - start def sequential_download(): 顺序执行 print( 顺序执行 ) start_time time.time() for url in URLS: result fetch_url(url) print(f{result[0]}: {result[1]} (耗时: {result[2]:.2f}s)) print(f总耗时: {time.time() - start_time:.2f} 秒\n) def concurrent_download(): 使用线程池并发执行 print( 使用线程池并发执行 ) start_time time.time() # 使用 with 语句管理执行器确保池子被正确关闭 with concurrent.futures.ThreadPoolExecutor(max_workers3) as executor: # 使用 submit 提交单个任务返回 Future 对象 # future_to_url {executor.submit(fetch_url, url): url for url in URLS} # 使用 map 提交一批任务更简洁 results executor.map(fetch_url, URLS) # 获取结果 for url, size, elapsed in results: print(f{url}: {size} (耗时: {elapsed:.2f}s)) print(f总耗时: {time.time() - start_time:.2f} 秒) if __name__ __main__: sequential_download() time.sleep(2) # 稍作停顿 concurrent_download()关键点max_workers指定了线程池中最大线程数。对于 I/O 密集型任务可以设置得比 CPU 核心数多一些。executor.map(func, iterable)是最常用的方式它按顺序提交任务并返回一个按提交顺序生成结果的迭代器。executor.submit(func, *args, **kwargs)提交单个任务返回一个Future对象可以通过future.result()获取结果会阻塞直到任务完成。使用with语句可以确保在所有任务完成后线程池被正确关闭。运行这个例子你会看到并发下载的总耗时远小于顺序下载各任务耗时之和因为线程在等待网络响应时可以切换去执行其他任务。6.2 使用 ProcessPoolExecutor只需将ThreadPoolExecutor替换为ProcessPoolExecutor代码结构几乎不变但底层变成了多进程适用于 CPU 密集型任务。import concurrent.futures import math import time PRIMES [ 112272535095293, 112582705942171, 112272535095293, 115280095190773, 115797848077099, 1099726899285419, 112272535095293, # 重复一些数字以增加计算量 112582705942171, ] def is_prime(n): 判断一个数是否为质数CPU密集型 if n 2: return False if n 2: return True if n % 2 0: return False sqrt_n int(math.floor(math.sqrt(n))) for i in range(3, sqrt_n 1, 2): if n % i 0: return False return True def sequential_check(): print( 顺序执行质数判断 ) start time.time() for number in PRIMES: prime is_prime(number) print(f{number} is prime: {prime}) print(f顺序执行耗时: {time.time() - start:.2f} 秒\n) def concurrent_check(): print( 使用进程池并发判断 ) start time.time() # 注意max_workers 通常设置为 CPU 核心数或略少 with concurrent.futures.ProcessPoolExecutor(max_workers4) as executor: # 使用 map将函数应用到可迭代对象的每个元素上 results executor.map(is_prime, PRIMES) for number, result in zip(PRIMES, results): print(f{number} is prime: {result}) print(f并发执行耗时: {time.time() - start:.2f} 秒) if __name__ __main__: sequential_check() time.sleep(1) concurrent_check()重要区别ProcessPoolExecutor在 Windows 和 macOS 上使用spawn启动方式因此主模块代码必须放在if __name__ __main__:之下。max_workers通常设置为机器的 CPU 核心数量。设置过多会因为进程切换开销导致性能下降。传递的参数和返回的结果必须是可序列化的picklable因为数据需要在进程间传递。7. 资源占用与性能观察编写并发程序时必须关注其资源消耗和性能表现。观察工具系统自带工具Windows任务管理器查看 CPU、内存、磁盘、网络。Linux/macOStop、htop、ps aux命令。Python 内置模块time/timeit测量代码执行时间。cProfile/profile分析函数调用耗时。memory_profiler(第三方库)分析内存使用情况。性能测试示例对比线程与进程import time import threading import multiprocessing import concurrent.futures def cpu_bound_calc(n): 模拟CPU密集型计算 count 0 for i in range(n): count i * i return count def io_bound_task(t): 模拟I/O密集型任务等待 time.sleep(t) return t def test_threads_cpu(workers, n): 多线程处理CPU密集型任务 with concurrent.futures.ThreadPoolExecutor(max_workersworkers) as executor: start time.time() list(executor.map(cpu_bound_calc, [n]*workers)) return time.time() - start def test_processes_cpu(workers, n): 多进程处理CPU密集型任务 with concurrent.futures.ProcessPoolExecutor(max_workersworkers) as executor: start time.time() list(executor.map(cpu_bound_calc, [n]*workers)) return time.time() - start def test_threads_io(workers, t): 多线程处理I/O密集型任务 with concurrent.futures.ThreadPoolExecutor(max_workersworkers) as executor: start time.time() list(executor.map(io_bound_task, [t]*workers)) return time.time() - start def test_processes_io(workers, t): 多进程处理I/O密集型任务 with concurrent.futures.ProcessPoolExecutor(max_workersworkers) as executor: start time.time() list(executor.map(io_bound_task, [t]*workers)) return time.time() - start if __name__ __main__: cpu_workers 4 cpu_n 5_000_000 io_workers 10 io_t 0.5 print( CPU密集型任务测试 (计算密集型) ) t_time test_threads_cpu(cpu_workers, cpu_n) p_time test_processes_cpu(cpu_workers, cpu_n) print(f多线程 ({cpu_workers} workers) 耗时: {t_time:.2f} 秒) print(f多进程 ({cpu_workers} workers) 耗时: {p_time:.2f} 秒) print(f进程比线程快: {t_time/p_time:.2f} 倍\n) print( I/O密集型任务测试 (睡眠模拟) ) t_time test_threads_io(io_workers, io_t) p_time test_processes_io(io_workers, io_t) print(f多线程 ({io_workers} workers) 耗时: {t_time:.2f} 秒) print(f多进程 ({io_workers} workers) 耗时: {p_time:.2f} 秒) # 对于纯I/O两者时间应接近但线程开销更小运行这个测试你可以直观地看到CPU 密集型多进程耗时远小于多线程理想情况下接近线程耗时/CPU核心数因为多进程绕过了 GIL。I/O 密集型多线程和多进程的耗时可能接近因为时间主要花在等待上。但多线程的创建和切换开销通常小于多进程。最佳实践监控资源运行程序时打开系统监控工具观察 CPU 使用率是否达到预期多进程应使多核饱和内存是否平稳。避免过度并发线程/进程数不是越多越好。I/O 密集型可以多一些如几十个CPU 密集型最好等于或略小于 CPU 核心数。使用池化始终优先使用ThreadPoolExecutor或ProcessPoolExecutor而不是手动创建大量线程/进程。8. 常见问题与排查方法并发编程中会遇到各种棘手的问题下表列出了一些典型问题及其解决方法。问题现象可能原因排查方式解决方案程序卡住无输出也不结束1. 死锁多个线程/进程互相等待对方释放锁。2. 队列操作阻塞Queue.get()空队列或Queue.put()满队列且无超时。3. I/O 操作无限等待如网络请求无超时。1. 使用threading.enumerate()或multiprocessing.active_children()查看活动线程/进程。2. 添加日志输出锁的获取和释放状态。3. 检查队列大小和生产者/消费者逻辑。4. 为所有网络/文件操作设置超时参数。1. 设计锁的获取顺序使用RLock或with语句管理锁。2. 使用Queue.get(timeout...)和Queue.put(timeout...)。3. 使用concurrent.futures的as_completed或wait设置超时。数据不一致或结果错误竞态条件多个线程同时读写共享变量未加锁。1. 检查所有对共享资源全局变量、文件、数据库连接的访问。2. 使用线程安全的数据结构如queue.Queue、collections.deque需配合锁。1. 使用threading.Lock或RLock保护临界区。2. 将共享数据访问封装到一个线程中通过队列与其他线程通信。多进程程序在 Windows 上报错或行为异常Windows 使用spawn方式创建进程子进程会重新导入主模块。检查是否将所有启动代码特别是创建新进程的代码放在了if __name__ __main__:块内。强制要求多进程代码的入口点必须是if __name__ __main__:。创建大量线程/进程后程序崩溃1. 达到系统资源限制如打开文件数、内存。2. 每个线程/进程开销过大。1. 观察系统资源使用情况。2. 使用ulimit -a(Linux) 查看限制。3. 使用池化技术限制并发数量。1. 使用线程池/进程池 (concurrent.futures)。2. 减少每个任务的内存占用。3. 考虑使用异步 I/O (asyncio) 处理大量连接。程序性能提升不明显甚至下降1. 任务并非瓶颈阿姆达尔定律。2. 并发开销创建、切换、通信抵消了收益。3. GIL 限制了多线程的 CPU 并行。1. 使用性能分析工具 (cProfile) 找到热点函数。2. 测试不同并发数下的性能。3. 区分任务是 I/O 密集型还是 CPU 密集型。1. 只对瓶颈部分进行并发优化。2. I/O 密集型用多线程或asyncioCPU 密集型用多进程。3. 调整线程池/进程池的大小。ThreadLocal数据丢失或混乱1. 在线程池中复用线程ThreadLocal数据未清理。2. 错误地访问了其他线程的数据实际上做不到可能是逻辑错误。1. 确保在任务开始前初始化ThreadLocal数据。2. 在任务结束后清理敏感数据如数据库连接。1. 使用线程池时在任务函数内部初始化ThreadLocal数据而不是在外部。2. 使用try...finally确保资源被清理。9. 最佳实践与使用建议掌握了基础之后遵循以下最佳实践能让你的并发程序更健壮、更高效。优先使用高层抽象除非有特殊需求否则优先使用concurrent.futures.ThreadPoolExecutor和ProcessPoolExecutor而不是手动管理threading.Thread或multiprocessing.Process。池化机制能自动管理生命周期避免资源泄漏。明确任务类型在编写并发代码前先分析任务是I/O 密集型还是CPU 密集型。这是选择多线程还是多进程的根本依据。合理设置并发数CPU 密集型max_workers设置为 CPU 核心数os.cpu_count()或略少。I/O 密集型可以设置得更高具体数值需要通过压测确定通常可以是核心数的几倍到几十倍但也要考虑目标系统的连接数限制。善用with语句使用with来管理执行器、锁、连接等资源可以确保它们被正确关闭和释放即使发生异常。避免共享状态多线程编程的万恶之源是共享可变状态。尽可能设计无状态的函数通过参数传递数据通过返回值获取结果。如果必须共享务必使用锁或其他同步机制。使用队列进行通信在多进程编程中multiprocessing.Queue是进程间通信最安全、最常用的方式。它比共享内存Value,Array更不容易出错。为任务设置超时特别是网络请求、文件读写等 I/O 操作必须设置超时防止某个失败的任务拖垮整个程序。可以使用concurrent.futures.as_completed的timeout参数。with ThreadPoolExecutor() as executor: future_to_url {executor.submit(load_url, url, 60): url for url in URLS} for future in concurrent.futures.as_completed(future_to_url, timeout10): url future_to_url[future] try: data future.result(timeout5) # 为单个任务结果获取也设置超时 except concurrent.futures.TimeoutError: print(f{url} request timed out) except Exception as exc: print(f{url} generated an exception: {exc})做好日志和错误处理并发程序中的异常不会自动崩溃主程序可能被静默吞掉。务必在任务函数内部做好try...except日志记录或者通过future.exception()检查任务是否出错。考虑asyncio作为替代如果你处理的是大量网络 I/O如 HTTP 请求、数据库连接asyncio协程模型比多线程更轻量、更高效。它使用单线程通过事件循环和await来切换任务避免了线程切换的开销和 GIL 的影响。Python 的并发编程工具箱非常丰富从底层的threading/multiprocessing到高层的concurrent.futures再到现代的asyncio。理解每种工具的原理和适用场景是写出高效、稳定并发程序的关键。建议从简单的线程池/进程池任务开始实践逐步深入到复杂的同步和通信场景并在实际项目中不断积累经验。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价