1. Python并发编程的本质困境
当你的Python脚本需要同时处理多个任务时,会立即面临一个根本性选择:用多线程还是多进程?我在处理一个爬虫项目时就栽过跟头——最初用多线程抓取电商数据,结果发现速度还不如单线程快。后来改用多进程,CPU利用率直接飙到90%,但内存消耗又成了新问题。
Python的全局解释器锁(GIL)就像个严格的交通警察,它规定任何时候只有一个线程能执行Python字节码。这意味着即便你开了100个线程,真正在干活的可能只有1个。但多进程就完全避开了这个限制,每个进程都有独立的GIL,代价则是更高的系统开销。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 多线程实战:I/O密集型场景的利器
2.1 threading模块的典型用法
上周我刚用多线程优化了一个文件处理工具,处理3000个CSV文件的时间从45分钟缩短到8分钟。关键代码其实很简单:
python复制import threading
def process_file(file_path):
# 文件读取和预处理逻辑
...
threads = []
for file in file_list:
t = threading.Thread(target=process_file, args=(file,))
t.start()
threads.append(t)
[t.join() for t in threads]
重要提示:线程数不是越多越好,一般取CPU核心数*2+1。我的MacBook Pro是8核,所以设置17个线程时效率最高。
2.2 线程安全那些坑
去年做的一个日志分析系统就遇到过死锁问题。两个线程互相等待对方释放锁,导致程序卡死。后来我养成了这样的编码习惯:
python复制lock = threading.Lock()
with lock:
# 操作共享资源
shared_list.append(data)
3. 多进程攻坚:榨干CPU的终极方案
3.1 multiprocessing模块实战
当处理视频转码这类CPU密集型任务时,多进程才是正解。这是我的常用模板:
python复制from multiprocessing import Pool
def encode_video(video_clip):
# 使用FFmpeg进行转码
...
if __name__ == '__main__':
with Pool(processes=4) as pool:
pool.map(encode_video, video_files)
3.2 进程间通信方案对比
上周帮同事调试一个分布式计算项目时,我们对比了三种通信方式:
| 方式 | 速度排名 | 适用场景 | 典型延迟 |
|---|---|---|---|
| Queue | 3 | 中小数据量 | 5-10ms |
| Pipe | 2 | 点对点通信 | 1-5ms |
| SharedMemory | 1 | 大数据量实时共享 | <1ms |
最终选择了SharedMemory方案,性能提升了20倍,但代码复杂度也显著增加。
4. 现代Python并发新选择
4.1 协程的崛起
在开发Web爬虫时,我发现aiohttp+asyncio组合比多线程更高效:
python复制import aiohttp
import asyncio
async def fetch(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
async def main():
tasks = [fetch(url) for url in url_list]
await asyncio.gather(*tasks)
4.2 线程池与进程池的混用技巧
上个月做的一个数据分析项目,我采用了这样的混合方案:
- 用ProcessPoolExecutor处理CPU密集型特征计算
- 用ThreadPoolExecutor处理数据库IO操作
- 主进程负责结果聚合
这种架构把原本需要6小时的任务压缩到了47分钟。
5. 性能优化实战记录
5.1 我的性能测试方法论
在优化一个图像处理服务时,我建立了这样的测试流程:
- 基准测试:单进程版本耗时(假设为100%)
- 多线程测试:记录不同线程数下的耗时
- 多进程测试:记录不同进程数下的耗时
- 混合模式测试:线程+进程的组合方案
最终得到的性能曲线图显示,当使用4进程+每进程2线程时,吞吐量达到单进程的6.8倍。
5.2 内存管理的血泪教训
曾经有个项目因为没控制好进程内存导致服务器崩溃。现在我会强制每个工作进程这样做:
python复制import resource
# 限制进程内存为2GB
soft, hard = resource.getrlimit(resource.RLIMIT_AS)
resource.setrlimit(resource.RLIMIT_AS, (2 * 1024**3, hard))
6. 选型决策树
根据我的经验,可以按这个流程图做技术选型:
code复制开始
│
├─ 需要并行CPU计算? → 是 → 用多进程
│ │
│ └─ 数据量巨大? → 是 → 考虑SharedMemory
│
├─ 主要是IO等待? → 是 → 用多线程/协程
│ │
│ └─ 需要高并发连接? → 是 → 优先asyncio
│
└─ 不确定? → 写基准测试对比三种方案
上周用这个方法帮团队选择了一个消息队列处理方案,避免了可能的技术债务。
