【python零基础教程第19讲】Python 并发编程

发布时间:2026/7/13 9:27:36
【python零基础教程第19讲】Python 并发编程 Python 并发编程全攻略从多线程到协程的完整实践指南前言在当今多核CPU和I/O密集型应用盛行的时代并发编程已成为Python开发者必须掌握的技能。Python提供了多种并发模型threading多线程、multiprocessing多进程以及asyncio协程。每种模型都有其适用场景和局限性。本文将从基础到进阶系统梳理Python并发编程的核心概念、常用工具和最佳实践并通过对比分析帮助你做出正确的技术选型。一、多线程threading1.1 线程基础线程是操作系统能够进行运算调度的最小单位。Python的threading模块基于操作系统原生线程实现但由于全局解释器锁GIL的存在同一时刻只有一个线程能执行Python字节码。因此多线程适合I/O密集型任务如网络请求、文件读写而不适合CPU密集型计算。importthreadingimporttimedefworker(name):print(f线程{name}开始工作)time.sleep(2)# 模拟I/O操作print(f线程{name}结束)# 创建线程t1threading.Thread(targetworker,args(A,))t2threading.Thread(targetworker,args(B,))# 启动线程t1.start()t2.start()# 等待线程结束t1.join()t2.join()print(所有线程执行完毕)1.2 锁Lock与RLock当多个线程同时访问共享资源时可能产生竞态条件。锁用于保证同一时刻只有一个线程能访问临界区。Lock互斥锁最基本的锁不可重入。同一个线程不能连续两次acquire()否则会死锁。RLock可重入锁允许同一个线程多次获取锁适用于递归调用或嵌套锁场景。importthreading counter0lockthreading.Lock()defincrement():globalcounterfor_inrange(100000):withlock:# 等价于 lock.acquire() 和 lock.release()counter1threads[threading.Thread(targetincrement)for_inrange(10)]fortinthreads:t.start()fortinthreads:t.join()print(f最终计数:{counter})# 正确结果应为 1000000RLock示例rlockthreading.RLock()defrecursive_func(n):withrlock:ifn0:recursive_func(n-1)# 同一线程可再次获取锁1.3 线程池ThreadPoolExecutor手动管理线程的创建和销毁效率低下推荐使用concurrent.futures.ThreadPoolExecutor。它内部维护一个线程池复用线程并提供了submit()和map()等便捷方法。fromconcurrent.futuresimportThreadPoolExecutor,as_completedimporttimedeffetch_url(url):time.sleep(1)# 模拟网络请求returnf结果来自{url}urls[http://example.com,http://python.org,http://github.com]withThreadPoolExecutor(max_workers3)asexecutor:# 方式1submit 返回 Future 对象futures[executor.submit(fetch_url,url)forurlinurls]forfutureinas_completed(futures):print(future.result())# 方式2map 直接返回结果迭代器# results executor.map(fetch_url, urls)# for result in results:# print(result)二、多进程multiprocessing2.1 进程基础多进程可以绕过GIL限制充分利用多核CPU。每个进程拥有独立的Python解释器和内存空间因此适合CPU密集型任务。但进程间通信IPC比线程间通信更复杂开销也更大。importmultiprocessingimportosdefcpu_intensive(n):计算斐波那契数列CPU密集型a,b0,1for_inrange(n):a,bb,abreturnaif__name____main__:withmultiprocessing.Pool(processes4)aspool:resultspool.map(cpu_intensive,[1000000,1000001,1000002,1000003])print(results)2.2 进程池Poolmultiprocessing.Pool提供了类似线程池的接口支持map、apply、starmap等方法。注意Pool必须在if __name__ __main__:保护下使用Windows平台要求。frommultiprocessingimportPooldefsquare(x):returnx*xif__name____main__:withPool(4)asp:print(p.map(square,range(10)))# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]2.3 进程间通信Queue由于进程内存独立不能直接共享变量。multiprocessing.Queue是线程安全的队列用于在进程间传递数据。importmultiprocessingdefproducer(queue):foriinrange(5):queue.put(f消息{i})print(f生产者发送: 消息{i})defconsumer(queue):whileTrue:msgqueue.get()ifmsgisNone:# 哨兵值表示结束breakprint(f消费者收到:{msg})if__name____main__:qmultiprocessing.Queue()p1multiprocessing.Process(targetproducer,args(q,))p2multiprocessing.Process(targetconsumer,args(q,))p1.start()p2.start()p1.join()q.put(None)# 发送结束信号p2.join()其他IPC方式Pipe管道、Value/Array共享内存需加锁、Manager更高级的共享对象。三、协程asyncio3.1 协程基础协程是用户态轻量级线程由程序自身控制调度切换开销极小。Python 3.5 引入async/await语法asyncio是标准库中的异步I/O框架。协程适合高并发I/O密集型任务如Web服务器、爬虫、API调用。// 更多关于协程操作 请移步 【python零基础教程第15讲】深入理解 Python 异步编程importasyncioasyncdeffetch_data(url):print(f开始请求{url})awaitasyncio.sleep(1)# 模拟异步I/Oprint(f完成请求{url})returnf数据来自{url}asyncdefmain():# 并发执行多个协程tasks[fetch_data(http://a.com),fetch_data(http://b.com),fetch_data(http://c.com),]resultsawaitasyncio.gather(*tasks)print(results)# 运行事件循环asyncio.run(main())3.2 异步上下文管理器与异步迭代器asyncio支持异步上下文管理器async with和异步迭代器async for用于管理资源或处理流式数据。importasyncioclassAsyncResource:asyncdef__aenter__(self):print(获取资源)returnselfasyncdef__aexit__(self,exc_type,exc_val,exc_tb):print(释放资源)asyncdefuse_resource():asyncwithAsyncResource()asres:print(使用资源中)asyncio.run(use_resource())3.3 异步队列asyncio.Queueasyncio.Queue是协程安全的队列常用于生产者-消费者模式。importasyncioasyncdefproducer(queue):foriinrange(5):awaitqueue.put(f任务{i})print(f生产: 任务{i})awaitasyncio.sleep(0.5)asyncdefconsumer(queue):whileTrue:itemawaitqueue.get()ifitemisNone:breakprint(f消费:{item})queue.task_done()asyncdefmain():qasyncio.Queue()prodasyncio.create_task(producer(q))consasyncio.create_task(consumer(q))awaitprodawaitq.put(None)# 结束信号awaitcons asyncio.run(main())四、线程、进程、协程对比与选型特性线程 (threading)进程 (multiprocessing)协程 (asyncio)资源开销中等每个线程约8MB栈空间高每个进程独立内存空间极低每个协程仅几KBGIL影响受GIL限制CPU密集型无法并行无GIL可真正并行无GIL单线程内并发适用场景I/O密集型、短任务CPU密集型、需要隔离的任务高并发I/O密集型、网络服务数据共享容易共享内存需加锁困难需IPC机制容易单线程内共享变量调度方式操作系统抢占式调度操作系统抢占式调度用户态协作式调度创建/切换开销较高很高极低调试难度中等竞态条件较低进程隔离较低单线程顺序执行库支持标准库广泛标准库广泛标准库但需异步生态选型建议I/O密集型任务如Web服务、文件读写、数据库查询如果并发量不大1000使用threading或ThreadPoolExecutor即可。如果并发量极大1000推荐asyncio资源消耗更低性能更好。CPU密集型任务如数值计算、图像处理、机器学习使用multiprocessing或concurrent.futures.ProcessPoolExecutor充分利用多核。混合型任务既有CPU计算又有I/O等待可结合使用用进程池处理CPU部分用协程或线程处理I/O部分。例如asyncio中通过run_in_executor将阻塞操作交给线程池。简单脚本或小工具优先考虑ThreadPoolExecutor或ProcessPoolExecutorAPI简洁无需手动管理生命周期。需要高并发且低延迟的网络服务首选asyncio如FastAPI、aiohttp配合uvloop可进一步提升性能。注意事项避免在协程中执行CPU密集型或阻塞I/O操作否则会阻塞事件循环。如需执行应使用loop.run_in_executor()将其委托给线程池。多进程编程时注意if __name__ __main__:保护避免无限递归创建子进程。使用锁时务必小心死锁。推荐使用with语句自动管理锁的获取与释放。对于共享数据优先考虑queue.Queue或multiprocessing.Queue而非直接操作共享变量。五、实战构建一个简单的并发下载器下面结合三种模型实现一个模拟下载多个文件的工具展示不同并发方式的用法。importasyncioimporttimefromconcurrent.futuresimportThreadPoolExecutor,ProcessPoolExecutor# 模拟下载函数defdownload_file(url):time.sleep(1)# 模拟I/Oreturnf下载完成:{url}asyncdefasync_download(url):awaitasyncio.sleep(1)returnf异步下载完成:{url}# 使用线程池defthread_download(urls):withThreadPoolExecutor(max_workers3)asexecutor:resultslist(executor.map(download_file,urls))returnresults# 使用进程池defprocess_download(urls):withProcessPoolExecutor(max_workers3)asexecutor:resultslist(executor.map(download_file,urls))returnresults# 使用协程asyncdefasync_download_all(urls):tasks[async_download(url)forurlinurls]returnawaitasyncio.gather(*tasks)if__name____main__:urls[fhttp://example.com/file{i}foriinrange(5)]# 线程池starttime.time()print(thread_download(urls))print(f线程池耗时:{time.time()-start:.2f}s)# 进程池starttime.time()print(process_download(urls))print(f进程池耗时:{time.time()-start:.2f}s)# 协程starttime.time()print(asyncio.run(async_download_all(urls)))print(f协程耗时:{time.time()-start:.2f}s)六、总结Python的并发编程生态丰富但选择正确的工具至关重要线程简单易用适合I/O密集型但受GIL限制。进程突破GIL适合CPU密集型但通信开销大。协程极致轻量适合高并发I/O但需要异步思维。在实际项目中往往需要组合使用多种并发模型。例如在asyncio应用中通过run_in_executor将阻塞操作交给线程池在multiprocessing中使用Manager或Queue进行进程间通信。掌握这些工具你就能游刃有余地应对各种并发场景。最后请务必注意并发编程中的常见陷阱死锁、竞态条件、资源泄漏、过度创建线程/进程等。合理使用锁、队列和池化技术并编写充分的测试才能构建稳定高效的并发程序。希望本文能帮助你全面理解Python并发编程并在实际开发中做出明智的选择。