问题与目标
并发不是“线程越多越快”。网络等待和大量计算的瓶颈不同,共享状态还可能让结果随机出错。本篇建立选择模型:I/O 密集任务优先考虑线程或异步,CPU 密集任务考虑多进程。
完成标准:能区分并发与并行,理解锁和 GIL 的边界,并用任务池分别处理 I/O 等待和 CPU 计算。
核心概念
并发表示多个任务在同一时间段推进,并行表示多个任务在同一时刻执行。线程共享进程内存,通信方便但需要保护共享写操作;进程有独立地址空间,隔离更强,但启动和数据传输成本更高。
在常见 CPython 中,GIL 限制多个线程同时执行 Python 字节码,因此线程通常不能加速纯 Python CPU 计算;I/O 等待期间线程仍可切换。锁只解决受保护临界区的竞争,不解决死锁、业务一致性或跨进程共享。
可运行实现
python
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
from time import perf_counter, sleep
def simulated_io(value: int) -> int:
sleep(0.2)
return value * 2
def cpu_work(limit: int) -> int:
return sum(number * number for number in range(limit))
def run_pool(pool_type, function, values):
started = perf_counter()
with pool_type(max_workers=4) as pool:
result = list(pool.map(function, values))
return result, perf_counter() - started
def main() -> None:
io_result, io_seconds = run_pool(ThreadPoolExecutor, simulated_io, range(8))
cpu_result, cpu_seconds = run_pool(
ProcessPoolExecutor, cpu_work, [1_000_000] * 4
)
print(io_result, round(io_seconds, 2))
print(len(cpu_result), round(cpu_seconds, 2))
if __name__ == "__main__":
main()
输入是两组任务参数,输出是有序结果和耗时。具体耗时依机器而异;重点是比较同类任务的串行和并发版本,而不是把一次结果当结论。
共享计数器需要锁住“读取—修改—写回”整体:
python
from threading import Lock, Thread
from time import sleep
lock = Lock()
counter = 0
def increase() -> None:
global counter
for _ in range(100):
with lock:
current = counter
sleep(0.0001)
counter = current + 1
threads = [Thread(target=increase) for _ in range(4)]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
print(counter)
输出应为 400。暂时去掉 with lock 后,故意加入的短暂停顿会放大竞争窗口,结果通常小于 400。这个实验说明复合操作需要保护,而不是说明所有变量访问都必须加锁。
进程不共享普通 Python 对象,结果可以通过队列显式传回:
python
from multiprocessing import Process, Queue
def calculate(value: int, output: Queue) -> None:
output.put((value, value * value))
if __name__ == "__main__":
output = Queue()
processes = [Process(target=calculate, args=(value, output)) for value in range(3)]
for process in processes:
process.start()
results = [output.get() for _ in processes]
for process in processes:
process.join()
print(sorted(results))
队列传递的是序列化后的数据,不是跨进程共享同一个对象。大量数据在进程间复制也会成为瓶颈。
常见问题与排查
- 多进程在 Windows/macOS 启动异常:进程池入口必须放在
if __name__ == "__main__"下。 - 向进程池传入无法序列化的对象:参数和函数应尽量简单、可 pickle。
- 工作线程过多反而变慢:线程切换、连接数和下游限流都有成本。
- 锁范围过大:并发退化为串行;锁范围过小则状态仍不一致。
- 任务抛错未被发现:必须消费
Future.result()或map()的结果,让异常回到调用方。 - 把线程池和进程池嵌套得很深:资源数量会失控;在系统入口统一设置任务池和并发上限。
小结
先判断瓶颈,再选工具:I/O 等待可用线程,CPU 计算可用进程,共享写操作才考虑锁。任务池管理生命周期和异常,比手工无限创建线程更稳妥。
#Python
#大模型
#AI
License: CC BY-NC 4.0
Updated 4 hours ago
Was this article helpful? Give it a like.
0 comments


