并发编程是Python提升程序执行效率的核心技能,不管是处理IO密集型任务(如网络请求、文件读写),还是CPU密集型任务(如数据计算、矩阵运算),掌握并发都能让程序"跑更快"。
一、核心认知:并发vs并行(新手必懂)
很多人容易混淆"并发"和"并行",先理清核心区别:
| 概念 | 核心定义 | 适用场景 |
|---|---|---|
| 并发(Concurrency) | 多个任务交替执行(同一时间只有一个任务在跑),看似"同时进行" | IO密集型任务(等待时间长) |
| 并行(Parallelism) | 多个任务真正同时执行(利用多核CPU),物理上的"同时进行" | CPU密集型任务(计算量大) |
Python实现并发/并行的三大核心方式:
- 多线程(threading):并发,适合IO密集型任务;
- 多进程(multiprocessing):并行,适合CPU密集型任务;
- 异步IO(asyncio):单线程并发,极致优化IO密集型任务。
二、多线程:IO密集型任务首选(threading模块)
1. 核心原理
线程是进程内的执行单元,共享进程内存空间,切换开销小。Python的threading模块实现多线程,但受GIL(全局解释器锁)限制,同一时间只有一个线程执行Python字节码------因此多线程仅对"等待型"IO任务有效(如网络请求、文件读写)。
2. 基础用法:创建和启动线程
python
import threading
import time
定义线程执行的函数(模拟IO任务:网络请求)
def fetch_url(url, delay):
print(f"线程{threading.current_thread().name}:开始请求{url}")
time.sleep(delay) 模拟网络等待
print(f"线程{threading.current_thread().name}:{url}请求完成")
if __name__ == "__main__":
start_time = time.time()
创建2个线程
t1 = threading.Thread(target=fetch_url, args=("https://baidu.com", 2), name="t1")
t2 = threading.Thread(target=fetch_url, args=("https://github.com", 2), name="t2")
启动线程
t1.start()
t2.start()
等待线程结束(主进程阻塞)
t1.join()
t2.join()
print(f"总耗时:{time.time() - start_time:.2f}秒")
执行结果:
arduino
线程t1:开始请求https://baidu.com
线程t2:开始请求https://github.com
线程t1:https://baidu.com请求完成
线程t2:https://github.com请求完成
总耗时:2.01秒
(单线程执行需4秒,多线程仅需2秒,体现IO任务并发优势)
3. 实用进阶:线程池(ThreadPoolExecutor)
手动创建大量线程会导致资源耗尽,concurrent.futures.ThreadPoolExecutor(高层封装)可限制线程数量、复用线程,是批量IO任务的首选。
python
from concurrent.futures import ThreadPoolExecutor
import time
def fetch_url(url, delay):
print(f"开始请求{url}")
time.sleep(delay)
return f"{url}请求完成"
if __name__ == "__main__":
start_time = time.time()
urls = [
("https://baidu.com", 2),
("https://github.com", 2),
("https://python.org", 2)
]
创建线程池(最多3个线程)
with ThreadPoolExecutor(max_workers=3) as executor:
批量提交任务
results = [executor.submit(fetch_url, url, delay) for url, delay in urls]
获取结果
for res in results:
print(res.result())
print(f"总耗时:{time.time() - start_time:.2f}秒")
执行结果:总耗时≈2秒(3个IO任务并发执行)。
4. 适用场景
- 网络请求(爬虫、接口调用)、文件读写、数据库操作等IO密集型任务;
- 任务切换频繁、等待时间长的场景;
- 需共享内存数据(线程共享进程内存,无需额外通信)。
三、多进程:CPU密集型任务首选(multiprocessing模块)
1. 核心原理
进程是独立的执行单元,每个进程有自己的GIL,可利用多核CPU并行执行。multiprocessing模块突破GIL限制,是CPU密集型任务的最优解,但进程切换开销比线程大。
2. 基础用法:创建和启动进程
python
import multiprocessing
import time
定义进程执行的函数(模拟CPU任务:计算)
def calculate_square(num):
print(f"进程{multiprocessing.current_process().pid}:计算{num}的平方")
time.sleep(1) 模拟计算耗时
return num * num
if __name__ == "__main__":
start_time = time.time()
创建2个进程
p1 = multiprocessing.Process(target=calculate_square, args=(10,))
p2 = multiprocessing.Process(target=calculate_square, args=(20,))
p1.start()
p2.start()
p1.join()
p2.join()
print(f"总耗时:{time.time() - start_time:.2f}秒")
执行结果:总耗时≈1秒(2个CPU任务并行执行)。
3. 实用进阶:进程池(Pool/ProcessPoolExecutor)
批量CPU任务推荐用进程池,避免手动创建大量进程:
python
from concurrent.futures import ProcessPoolExecutor
import time
def calculate_square(num):
time.sleep(1)
return num * num
if __name__ == "__main__":
start_time = time.time()
nums = [1,2,3,4,5,6,7,8]
创建进程池(适配CPU核心数)
with ProcessPoolExecutor() as executor:
results = executor.map(calculate_square, nums)
print("计算结果:", list(results))
print(f"总耗时:{time.time() - start_time:.2f}秒")
执行结果:8个任务,4核CPU并行执行,总耗时≈2秒(单进程需8秒)。
4. 适用场景
- 数据计算、矩阵运算、图片处理、密码破解等CPU密集型任务;
- 需充分利用多核CPU的场景;
- 任务间无频繁数据共享(进程内存隔离,通信开销大)。
四、异步IO:极致IO密集型优化(asyncio模块)
1. 核心原理
异步IO是单线程内的"非阻塞"并发,通过事件循环(Event Loop)管理任务,当一个任务等待IO时,自动切换到其他任务执行------比多线程更轻量(无线程切换开销),是IO密集型任务的极致优化方案。
2. 核心概念(新手必记)
- 协程(Coroutine) :异步执行的函数,用
async def定义; - 事件循环(Event Loop):协程的调度器,负责管理任务执行;
- await:标记IO等待点,触发任务切换;
- Task:封装协程,加入事件循环执行。
3. 基础用法:简单异步任务
python
import asyncio
import time
定义异步函数(协程)
async def fetch_url(url, delay):
print(f"开始请求{url}")
await asyncio.sleep(delay) 异步等待(非阻塞)
print(f"{url}请求完成")
return url
async def main():
start_time = time.time()
创建协程任务
task1 = asyncio.create_task(fetch_url("https://baidu.com", 2))
task2 = asyncio.create_task(fetch_url("https://github.com", 2))
等待所有任务完成
await task1
await task2
print(f"总耗时:{time.time() - start_time:.2f}秒")
运行事件循环
if __name__ == "__main__":
asyncio.run(main())
执行结果:总耗时≈2秒(单线程内2个IO任务并发)。
4. 实用进阶:批量异步任务
python
import asyncio
import time
async def fetch_url(url, delay):
await asyncio.sleep(delay)
return f"{url}请求完成"
async def main():
start_time = time.time()
urls = [
("https://baidu.com", 2),
("https://github.com", 2),
("https://python.org", 2)
]
批量创建任务
tasks = [asyncio.create_task(fetch_url(url, delay)) for url, delay in urls]
等待所有任务完成
results = await asyncio.gather(*tasks)
for res in results:
print(res)
print(f"总耗时:{time.time() - start_time:.2f}秒")
if __name__ == "__main__":
asyncio.run(main())
执行结果:3个IO任务单线程并发,总耗时≈2秒。
5. 适用场景
- 高并发IO任务(如百万级网络请求、高并发接口服务);
- 追求极致性能的IO密集型场景;
- 单线程内的非阻塞任务(如异步Web框架FastAPI)。
五、三大并发模式对比(选型核心)
| 特性 | 多线程(threading) | 多进程(multiprocessing) | 异步IO(asyncio) |
|---|---|---|---|
| 核心优势 | 轻量、共享内存 | 多核并行、突破GIL | 极致轻量、无切换开销 |
| 核心劣势 | 受GIL限制、线程安全 | 内存隔离、通信开销大 | 代码改造成本高、仅支持异步库 |
| 适用任务 | IO密集型 | CPU密集型 | 高并发IO密集型 |
| 资源开销 | 低 | 高 | 极低 |
| 代码复杂度 | 低 | 中 | 高 |
六、实战案例:不同场景的并发选型
场景1:爬虫(IO密集型)
- 推荐方案:异步IO(asyncio + aiohttp)
python
import asyncio
import aiohttp 异步HTTP库
async def fetch(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return f"{url}:状态码{response.status}"
async def main():
urls = [
"https://baidu.com",
"https://github.com",
"https://python.org"
]
tasks = [asyncio.create_task(fetch(url)) for url in urls]
results = await asyncio.gather(*tasks)
for res in results:
print(res)
if __name__ == "__main__":
asyncio.run(main())
场景2:数据计算(CPU密集型)
- 推荐方案:多进程(ProcessPoolExecutor)
python
from concurrent.futures import ProcessPoolExecutor
import numpy as np
def matrix_calculate(matrix):
矩阵乘法(CPU密集)
return np.dot(matrix, matrix)
if __name__ == "__main__":
生成10个随机矩阵
matrices = [np.random.rand(1000, 1000) for _ in range(10)]
with ProcessPoolExecutor() as executor:
results = executor.map(matrix_calculate, matrices)
print("矩阵计算完成,共处理", len(list(results)), "个矩阵")
场景3:文件批量处理(IO+轻量计算)
- 推荐方案:多线程(ThreadPoolExecutor)
python
from concurrent.futures import ThreadPoolExecutor
import os
def process_file(filepath):
读取文件+简单处理(IO+轻量计算)
with open(filepath, "r", encoding="utf-8") as f:
content = f.read()
统计字符数
return f"{filepath}:字符数{len(content)}"
if __name__ == "__main__":
获取当前目录下的所有txt文件
files = [f for f in os.listdir() if f.endswith(".txt")]
with ThreadPoolExecutor(max_workers=5) as executor:
results = executor.map(process_file, files)
for res in results:
print(res)
七、避坑指南:新手常见错误
1. 坑1:用多线程处理CPU密集型任务
- 现象:程序运行速度比单线程还慢;
- 原因:GIL限制+线程切换开销;
- 解决方案:改用多进程。
2. 坑2:Windows系统多进程未加if __name__ == "__main__"
- 现象:报错
RuntimeError: An attempt has been made to start a new process; - 原因:Windows创建进程时重新导入脚本,递归创建进程;
- 解决方案:所有创建进程的代码放在
if __name__ == "__main__"下。
3. 坑3:异步代码中调用同步阻塞函数
- 现象:异步任务失去并发优势;
- 原因:
time.sleep()、requests.get()等同步函数会阻塞事件循环; - 解决方案:改用异步库(如
asyncio.sleep()、aiohttp)。
4. 坑4:过度并发导致资源耗尽
- 现象:程序崩溃、服务器连接被拒绝;
- 原因:线程/进程/异步任务数量过多;
- 解决方案:限制并发数(如线程池
max_workers、异步任务限流)。