当前位置:首页>python>AI大模型python3复习:8 并发与异步

AI大模型python3复习:8 并发与异步

  • 2026-10-11 06:37:53
AI大模型python3复习:8 并发与异步

你好,我是老李。

九块路线总览见《Python 到 FastAPI 九层进阶》。第六块解决了单条执行路径如何留下失败证据;当任务同时推进,问题会多出共享状态、取消传播和资源上限。先问瓶颈在哪里,再谈线程、进程或协程。

老李排查过一个“线程越多越慢”的抓取服务:请求很快,却把响应解析和压缩都塞在线程池里,CPU 争用与锁等待同时放大。并发不是数量按钮,而是一组资源与调度约束。

这一块的验收标准是:能依据 I/O 等待和 CPU 计算选择模型,用锁或消息传递保护状态,并为超时、取消和背压设计出口

01、并发模型先匹配瓶颈

这一块沿着“解释器约束—线程—进程—协程—协作原语—统一接口”展开。示例刻意保持结果确定,便于验证并发控制是否真实生效。

02、GIL 状态取决于解释器构建

7.1 传统 GIL 与自由线程构建

官方概念

传统 CPython 构建使用全局解释器锁协调 Python 字节码执行;Python 3.13 起提供实验性自由线程构建,但它是单独配置,并非所有安装的默认状态。

类比记忆

像办公区有一枚共享通行牌;自由线程构建尝试撤掉通行牌,却要求家具、门锁和第三方扩展都重新接受并发检查。

import sysconfig

free_threaded = bool(sysconfig.get_config_var("Py_GIL_DISABLED"))
print("自由线程构建" if free_threaded else "传统构建")
print(type(free_threaded).__name__)

运行输出:

传统构建
bool

使用边界

自由线程支持仍在演进,扩展模块可能声明不支持并重新启用 GIL。无论构建类型如何,复合读改写都不能凭直觉当作原子操作。

03、线程适合重叠阻塞等待

7.2 threading、Lock 与共享状态

官方概念

线程共享同一进程内存,创建成本较低,适合等待网络或磁盘;共享可变状态必须用锁、队列或不可变数据明确同步。

类比记忆

像多人共用一块白板:交流快,但同时改同一格会覆盖;锁是一次只允许一人书写的白板笔。

from threading import Lock, Thread

total = 0
lock = Lock()

def add_many() -> None:
    global total
    for _ in range(1000):
        with lock:
            total += 1

threads = [Thread(target=add_many) for _ in range(4)]
for thread in threads:
    thread.start()
for thread in threads:
    thread.join()
print(total)

运行输出:

4000

使用边界

锁范围过大会串行化,多个锁顺序不一致可能死锁。CPU 密集型纯 Python 工作在传统构建中通常不因增加线程获得线性加速。

04、进程用隔离换取多核执行

7.3 multiprocessing 与进程通信

官方概念

进程拥有独立解释器和地址空间,可使用多个 CPU 核心;参数和结果通常需要序列化,通过队列、管道或共享内存通信。

类比记忆

像把工作分到不同厨房:可同时开火且互不碰撞,但食材和成品必须经过传送口,搬运也有成本。

from multiprocessing import get_context

def double(value: int, output) -> None:
    output.put(value * 2)

if __name__ == "__main__":
    context = get_context("fork")
    output = context.Queue()
    process = context.Process(target=double, args=(21, output))
    process.start()
    print(output.get())
    process.join()

运行输出:

42

使用边界

Windows 和 macOS 常用 spawn,入口必须由 if __name__ == "__main__" 保护,可调用目标需可导入。大对象传输可能抵消并行收益。

05、协程在等待点主动交还控制权

7.4 asyncio、await 与 TaskGroup

官方概念

事件循环在协程执行到 await 时调度其他任务;TaskGroup 自 Python 3.11 提供结构化并发,退出作用域前等待任务并统一传播失败。

类比记忆

像一名厨师同时照看多口锅:水沸等待时切换到备菜,但切菜本身若耗时过长,其他锅仍无人照看。

import asyncio

async def fetch(name: str, delay: float) -> str:
    await asyncio.sleep(delay)
    return name

async def main() -> None:
    async with asyncio.TaskGroup() as group:
        first = group.create_task(fetch("A", 0.02))
        second = group.create_task(fetch("B", 0.01))
    print(sorted([first.result(), second.result()]))

asyncio.run(main())

运行输出:

['A', 'B']

使用边界

协程适合高并发 I/O,不会自动加速阻塞函数或 CPU 计算。同步阻塞调用需换异步驱动、交给线程,或拆到进程。

06、超时、队列与取消共同限制失控

7.5 Queue、wait_for 与 to_thread

官方概念

有界队列提供背压,wait_for 为等待设置期限,取消信号让无用工作尽快退出;to_thread 可把短期无法替换的阻塞函数移出事件循环线程。

类比记忆

像有容量的候车厅:满员就暂停放行,末班时间到了便取消行程,临时人工窗口负责旧式纸质流程。

import asyncio

async def main() -> None:
    queue = asyncio.Queue(maxsize=1)
    await queue.put("任务")
    item = await asyncio.wait_for(queue.get(), timeout=0.1)
    queue.task_done()
    total = await asyncio.to_thread(sum, [1, 2, 3])
    print(item, total, queue.empty())

asyncio.run(main())

运行输出:

任务 6 True

使用边界

超时后要确认底层操作是否真正取消,尤其是线程中的阻塞调用。队列必须有容量、消费者退出协议和失败处置,不能只做无限缓冲。

07、Executor 统一提交与收集方式

7.6 concurrent.futures 线程池与进程池

官方概念

Executor 用 submit、map 和 Future 统一任务接口;线程池面向阻塞 I/O,进程池面向可序列化的 CPU 任务。map 按输入顺序给出结果。

类比记忆

像统一派工台:工单格式相同,后场可换成共享办公室或独立车间,但每种后场的运输和隔离成本不同。

from concurrent.futures import ThreadPoolExecutor

def normalize(name: str) -> str:
    return name.strip().upper()

with ThreadPoolExecutor(max_workers=2) as executor:
    results = list(executor.map(normalize, [" a", "b ", " c "]))
print(results)

运行输出:

['A', 'B', 'C']

使用边界

池大小必须服从下游连接数、内存和限流约束。Future.result() 会重新抛出任务异常;若只提交不收集,失败可能被延迟发现。

08、三条工程洞见:并发上限来自最慢资源

洞见一:吞吐由瓶颈资源决定

事实:线程、进程和协程都不能增加下游数据库容量。
因果链:盲目加并发 → 排队与超时增加 → 重试继续放大流量。
影响:按连接池、限流与延迟预算设置并发上限,系统更稳定。
边界:离线批处理可在隔离资源下采用不同上限。

洞见二:结构化并发收束任务生命周期

事实:游离任务可能活过创建它的请求。
因果链:父任务结束 → 子任务继续占资源 → 异常无人接收。
影响:作用域统一等待和取消,让完成条件可证明。
边界:长期守护任务需要独立监督器而非请求作用域。

洞见三:消息传递减少共享写入

事实:共享可变状态需要同步并形成争用点。
因果链:写者增加 → 锁竞争上升 → 时序缺陷难复现。
影响:按所有者处理状态、通过消息交接,可缩小临界区。
边界:消息也需容量、幂等和失败重放策略。

09、来源、易混概念与下一块问题

权威来源与延伸阅读

  1. threading 线程
  2. multiprocessing 进程
  3. asyncio 异步 I/O
  4. asyncio TaskGroup
  5. asyncio 队列
  6. concurrent.futures
  7. queue 同步队列
  8. PEP 703 自由线程提案
  9. Python 自由线程使用说明
  10. PEP 779 自由线程支持状态
  11. 协程与任务

容易混淆的概念

并发与并行:并发让任务交错推进,并行让任务同一时刻执行;线程与进程:线程共享内存且启动轻,进程隔离内存且可使用多核;协程与线程:协程在显式等待点协作切换,线程由系统调度;超时与取消:超时是期限策略,取消是传播停止请求,底层操作未必立即终止。

核心小结

并发设计先识别工作负载:等待型任务可选线程或协程,CPU 型任务评估进程或经验证的自由线程构建。共享状态必须同步,任务必须有作用域,队列必须有容量,超时之后仍要确认资源真正释放。下一块将用高级语言机制提炼这些重复控制结构。

最新文章

随机文章