Python-模块篇(并发编程篇、threading、multiprocessing、asyncio)
1、关键概念
- 并发 (Concurrency) vs 并行 (Parallelism)
- 并发:宏观上同时处理多件事(threading 和 asyncio),微观上可能同一时刻只做一个。(交替处理多个任务)
- 并行:微观上真正同时做多件事(multiprocessing),需要多核CPU支持。(同时处理多个任务)
- GIL(全局解释器锁)的通俗解释
- Python的 "大锁" ,让一个进程同一时间只能执行一个线程。
- 所以threading 做不了真正的并行计算,而 multiprocessing 通过独立进程绕过了它。(线程执行前必须先获得GIL锁)
- 同步 vs 异步
- 同步:做完一件事再做下一件。(排队买票)
- 异步:发起一件事后不等待,先去处理别的,有结果了再回来处理。(点餐后干其它的,等菜上好再吃饭)
- 进程(Process) vs 线程(Thread)
- 进程:正在执行的程序,是CPU、内存等资源分配的基本单位
- 线程:包含在进程中,实际是CPU调度执行具体任务的最小单位
2、适合场景
| 并发方式 | 别名 | 适合场景 | 总结 |
| threading | 多线程 |
I/O密集型任务(网络请求、文件读写) 1、同时只能使用CPU的一个核心 (任务数量适中(几十到几百)) |
轻量级,切换快(线程之间能共享资源),但要注意线程安全。 1、安全问题:多个线程同时操作一个共享数据时,可能导致数据不准确 2、解决:代码加锁(threading.Lock()) 3、死锁:两个线程都在等待获取对方的锁,导致程序暂停 |
| multiprocessing | 多进程 |
CPU密集型任务(复杂计算、图像处理) 1、充分利用多核CPU |
资源开销大(进程之间不能共享资源),但能绕过GIL,实现真正的并行。 |
| asyncio | 异步I/O |
高并发I/O密集型任务 (任务数量巨大(成千上万)) (大量网络爬虫、高性能Web服务器) |
单线程内实现高并发,资源消耗极低,但代码有侵入性(async/await)。 1、侵入性:使用(async/await)实现异步,需要对原代码的调用链、逻辑结构和运行逻辑进行调整 |
3、threading 模块:轻量级并发
- 注意:修改共享变量时记得加锁(
threading.Lock()),防止数据错乱。 - 函数式多线程:适合只执行一个函数,不需要保存线程状态。
- 类式多线程:适合需要为每个线程保存独立的属性(如 result、passed、error_msg)。
import threading import time # 方式1:直接传入函数 def request_api(url): print(f"正在请求: {url}") time.sleep(2) # 模拟I/O等待 print(f"请求完成: {url}") threads = [] for url in ["url1", "url2", "url3"]: #创建线程 t = threading.Thread(target=request_api, args=(url,)) threads.append(t) t.start() #创建并启动一个新的线程,新线程会并发执行 target 函数 for t in threads: t.join() # 主线程等待所有线程完成(防止主线程提前退出) print("所有请求完成") |
import threading import time # 方式2:类式多线程 |
4、multiprocessing 模块:绕过GIL的并行
from multiprocessing import Pool def cpu_bound_task(n): """模拟CPU密集型计算,如大量数据加密""" print(f"进程 {n} 开始计算...") result = sum(i * i for i in range(10000000)) return result if __name__ == "__main__": with Pool(processes=4) as pool: # 创建4个进程的进程池 results = pool.map(cpu_bound_task, range(5)) #此处创建5个任务,而进程池只有4个,因此有一个任务会排队等待执行 #p.map():批量分配任务(自动拆分 iterable,依次分配给每个进程),会阻塞主进程,直到所有子进程任务完成并返回结果 #p.apply_async():每次只能提交一个任务给进程执行,适合需要逐个灵活提交任务的场景。 print(results)
5、asyncio 模块:单线程高并发
- 协程(Coroutine):一个可暂停的函数
- async:创建协程
- Task:协程的包装器,被事件循环调度和执行(协程本身不能并发,Task 才能并发)
- await:等待一个已有的“可等待对象”(Awaitable)完成。
- async:创建协程
- 执行流程
| 1、定义协程 | async def fetch_data() | 定义一个可暂停的函数(协程),内部使用 await 等待异步操作。 |
| 2、调用协程 | coro = fetch_data() | 调用协程函数会返回一个协程对象,但不会执行其中的代码。 |
| 3、创建Task | task = asyncio.create_task(coro) | 将协程包装成Task并提交给事件循环,Task会立即开始调度(但未必马上执行)。 |
| 4、并发执行 |
results = await asyncio.gather(task1, task2) results = asyncio.wait(task1, task2) |
并发等待多个Task全部完成,并统一收集返回值。 1、asyncio.gather():主要用于等全部完成,直接返回结果列表 2、asyncio.wait():支持等任一完成、等全部完成、等超时等,返回 (done, pending) 任务集合 |
| 5、运行入口 | asyncio.run(main()) |
创建并启动事件循环,运行入口协程( 1、自动管理事件循环的生命周期(创建、运行、关闭) |
| 6、暂停与恢复 | result = await some_awaitable |
|
| 7、获取结果 | value = task.result() |
获取已完成Task的返回值 |
#安装aiohttp:pip install aiohttp import asyncio import aiohttp #使用 async 定义异步函数 async def fetch_url(session, url): print(f"开始请求: {url}") async with session.get(url) as response: # await 模拟等待网络响应,此时不会阻塞其他任务 html = await response.text() print(f"完成请求: {url}, 长度: {len(html)}") return html async def main(): urls = ["http://httpbin.org/get", "http://httpbin.org/ip", "http://httpbin.org/user-agent"] async with aiohttp.ClientSession() as session: tasks = [fetch_url(session, url) for url in urls] #asyncio.gather 并发执行多个任务。 #gather 接收到协程对象时,会自动调用 create_task() 将其包装成 Task results = await asyncio.gather(*tasks) # return results # 运行主协程 if __name__ == "__main__": asyncio.run(main())
本文来自博客园,作者:FengweiTech,转载请注明原文链接:https://www.cnblogs.com/fengwei-blogs/p/21300028

浙公网安备 33010602011771号