在 CentOS 上写 Python 并发程序,最常见的问题不是“怎么开并发”,而是“这类任务到底该用哪种并发模型”。线程、进程、线程池、协程都能把任务同时推进,但它们针对的瓶颈并不一样,选错之后不仅提速有限,甚至可能比单线程更慢。
这篇文章按实际选择路径来梳理常见方案:先区分 I/O 密集型和 CPU 密集型,再看 `threading`、`multiprocessing`、`concurrent.futures` 和 `asyncio` 各自适合什么场景、代码怎么写、限制在哪里。看完后,你至少能快速判断:当前任务该优先用线程、进程,还是直接上线程池、进程池或协程。
先判断任务类型,再决定并发方案
在 Python 里,并发方案的选择首先取决于任务瓶颈。
- I/O 密集型任务:大部分时间花在等待网络、磁盘、数据库响应上,例如网络请求、文件读写。
- CPU 密集型任务:主要时间耗在计算上,例如图像处理、数值计算。
如果是 I/O 密集型任务,通常优先考虑 threading、ThreadPoolExecutor 或 asyncio。如果是 CPU 密集型任务,则更适合 multiprocessing 或 ProcessPoolExecutor,因为它们能绕过 GIL(全局解释器锁)带来的限制。
I/O 任务不多时,先看 threading
threading 是 Python 自带的多线程方案,适合网络请求、文件读写这类 I/O 密集型任务。它的优点是上手快,写法直接,适合并发量不大、逻辑也比较简单的场景。
import threading
def worker(num):
"""线程任务函数"""
print(f"Worker: {num}")
threads = []
for i in range(5):
t = threading.Thread(target=worker, args=(i,))
threads.append(t)
t.start()
for t in threads:
t.join()
这段代码会创建 5 个线程,每个线程执行一次 worker(),最后通过 join() 等待所有线程结束。
它适合什么场景
如果你的程序主要是在“等”,而不是在“算”,多线程通常能把空闲等待时间利用起来。例如同时发起多个网络请求,或者同时处理多个文件读写任务。
需要注意什么
多线程并不等于适合所有任务。由于 Python 的 GIL 存在,threading 在 CPU 密集型任务上的表现通常不理想,所以不要把它当成通用加速工具,更不适合拿去做大量纯计算。
CPU 密集型任务,更适合 multiprocessing
当任务主要消耗 CPU 时,multiprocessing 往往比多线程更合适。原因在于它采用多进程模型,每个进程都有独立的 Python 解释器,可以真正利用多核 CPU 并行执行。
import multiprocessing
def worker(num):
"""进程任务函数"""
print(f"Worker: {num}")
processes = []
for i in range(5):
p = multiprocessing.Process(target=worker, args=(i,))
processes.append(p)
p.start()
for p in processes:
p.join()
这段代码同样启动 5 个执行单元,但这次是 5 个独立进程,而不是 5 个线程。
它为什么能绕过 GIL
GIL 会限制同一时刻只有一个线程执行 Python 字节码,但它不会跨进程生效。也就是说,多进程方案可以把计算任务分摊到多个 CPU 核心上,这也是它更适合图像处理、数值计算等场景的原因。

它的代价是什么
进程的创建、销毁以及进程间通信开销都比线程更高。因此,如果任务本身很轻,或者本来就是 I/O 等待为主,多进程未必划算。
想少管细节,可以直接用 concurrent.futures
如果你不想手动管理线程和进程的创建、回收,concurrent.futures 是更省心的选择。它提供了线程池和进程池的统一接口,适合需要限制并发数量、批量提交任务的场景。
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
def worker(num):
"""任务函数"""
print(f"Worker: {num}")
# 使用线程池
with ThreadPoolExecutor(max_workers=5) as executor:
for i in range(5):
executor.submit(worker, i)
# 使用进程池
with ProcessPoolExecutor(max_workers=5) as executor:
for i in range(5):
executor.submit(worker, i)
相比直接操作 threading 或 multiprocessing,线程池和进程池的好处在于接口统一,代码更短,也更容易控制并发上限。
什么时候优先选它
当你已经明确任务类型,但又不想自己维护线程列表、进程列表时,线程池和进程池通常是更实用的默认选项。尤其是在服务端脚本、批处理任务里,这种写法更容易维护。
高并发 I/O 场景下,再考虑 asyncio
asyncio 也是面向 I/O 密集型任务的,但它的思路和多线程不同。它通过协程(coroutine)在单线程内完成异步并发,不依赖多线程或多进程,运行开销通常更低,特别适合高并发网络编程。

import asyncio
async def worker(num):
"""异步任务函数"""
print(f"Worker: {num}")
await asyncio.sleep(1)
async def main():
tasks = [worker(i) for i in range(5)]
await asyncio.gather(*tasks)
asyncio.run(main())
这里的 await asyncio.sleep(1) 用来模拟 I/O 等待,比如等待数据库查询或网络响应。多个任务会在等待期间交出执行权,让事件循环继续推进其他任务。
它适合什么类型的程序
如果你面对的是大量网络连接、海量请求或需要维护很多并发 I/O 会话的程序,asyncio 往往比传统线程方案更轻量。
它的边界也很明确
asyncio 只能很好地处理 I/O 密集型任务。一旦协程内部塞进大量 CPU 计算,就会阻塞整个事件循环,导致整体并发能力下降。因此,它并不是 CPU 任务的替代方案。
CentOS 上做 Python 并发,实际可以这样选
如果只看落地选择,可以按下面这个判断思路来:
- CPU 密集型任务:首选
multiprocessing或ProcessPoolExecutor - I/O 密集型任务,且任务量不大:可用
threading或ThreadPoolExecutor - I/O 密集型任务,且并发量很高:例如上千个网络连接,更适合
asyncio - 想兼顾易用性和控制力:优先考虑
concurrent.futures的线程池或进程池
没有哪一种方案能覆盖所有情况。真正影响性能的,不是“用了并发”这件事本身,而是并发模型是否和任务瓶颈匹配。先分清自己是在等 I/O,还是在吃 CPU,再选工具,通常就不会走偏。







