为何在多文件处理场景下Threading比Asyncio快得多?
测试场景与代码
我正在测试Python的asyncio、threading与multiprocessing,以确定大量文件处理场景下的最优并发机制。实验内容为读取文件,在每行末尾追加字符后写回,测试代码如下:
import asyncio import threading from multiprocessing import Process async def process_files(filenames): for filename in filenames: await process_file(filename) async def process_file(filename): i = 0 #await asyncio.sleep(0.1) with open(filename, 'r+') as f: lines = f.readlines() for line in lines: line += f"{i}\n" f.write(line) i += 1 def regular_process_file(filename): i = 0 with open(filename, 'r+') as f: lines = f.readlines() for line in lines: line += f"{i}\n" f.write(line) i += 1 # asyncio调用 asyncio.run(process_files(filenames)) # 多线程调用 threads = [] for file in filenames: t = threading.Thread(target=regular_process_file, args=(file,)) t.start() threads.append(t) for t in threads: t.join() # 多进程调用 processes = [] for file in filenames: proc = Process(target=regular_process_file, args=(file,)) processes.append(proc) proc.start() for p in processes: p.join()
测试结果与疑问
对三种机制计时后发现:asyncio耗时与multiprocessing接近,threading耗时约为前两者的2/3。我原本的预期是:受GIL限制,asyncio与threading效率应相近;若threading能利用多处理器,性能则应与multiprocessing接近,实际结果与预期不符,对此存在疑问。
核心原因解析
1. 你的asyncio代码并非真正异步执行
当前代码中的文件读写使用的是Python原生的同步IO接口(open、readlines、write),这些操作会阻塞asyncio的事件循环。在执行await process_file(filename)时,事件循环会被完全阻塞,直到该文件处理完成才会切换到下一个任务——本质上是串行处理所有文件,而非并发。这也是asyncio耗时与multiprocessing接近的原因:multiprocessing是并行处理但存在进程创建/切换的额外开销,而asyncio是串行处理无额外开销,两者耗时因此拉平。
2. threading在IO密集场景下的优势
Python的GIL(全局解释器锁)确实会限制同一时间只有一个线程执行CPU密集型任务,但在执行阻塞式系统调用(如文件IO)时,线程会主动释放GIL,让其他线程可以获取GIL并执行。因此在文件处理这种IO密集场景下,多线程可以同时等待不同文件的IO操作,实现高效并发。同时,线程的创建与切换开销远小于进程,这也是threading耗时比multiprocessing短的核心原因。
3. multiprocessing的开销问题
multiprocessing通过创建独立的Python解释器进程绕过GIL,实现真正的并行执行,但进程的创建、内存复制、进程间通信都存在显著开销。在IO密集的文件处理场景下,这些开销抵消了并行带来的收益,导致其耗时不如threading。
优化建议
让asyncio实现真正异步
要让asyncio发挥出IO密集场景的优势,需要使用异步文件IO库(如aiofiles)替代同步IO接口,同时使用asyncio.gather并发执行所有文件处理任务,示例代码如下:
import asyncio import aiofiles async def process_file(filename): i = 0 async with aiofiles.open(filename, 'r+') as f: lines = await f.readlines() for line in lines: line += f"{i}\n" await f.write(line) i += 1 async def process_files(filenames): await asyncio.gather(*[process_file(filename) for filename in filenames]) asyncio.run(process_files(filenames))
修改后,asyncio的事件循环可以在等待文件IO时切换到其他任务,实现低开销的并发处理,性能会接近甚至超过threading。
三种机制的适用场景总结
- threading:适合IO密集型任务,线程开销小,IO操作时自动释放GIL,能高效并发。
- multiprocessing:适合CPU密集型任务,绕过GIL实现真正并行,但进程开销大。
- asyncio:适合高并发的IO密集型任务(如网络请求、大量文件处理),任务切换开销极小,但需配套使用异步IO接口。
内容的提问来源于stack exchange,提问作者Ames ISU

