Skip to content

027. LangServe 接口、同步阻塞与线程处理

学习目标

这一节继续讲 LangServe 自动生成的接口,并重点说明 Python 后端里同步调用、异步调用和线程处理的区别。

学完后,你应该能理解:

  • LangServe 会为 runnable 暴露哪些接口;
  • invokebatchstream 与异步方法是什么关系;
  • 同步 HTTP 请求和异步 HTTP 请求有什么区别;
  • 同步代码为什么会阻塞 FastAPI;
  • 死循环为什么即使写在异步接口里也会卡住服务;
  • 如何用线程把同步任务转成不阻塞主服务的调用。

LangServe 自动生成的接口

LangServe 挂载 runnable 后,会自动生成一些接口。

常见包括:

  • invoke
  • batch
  • stream

runnable 对象本身也有类似方法。

除了同步方法,还可能有异步版本,例如异步 invoke、异步 batch、异步 stream。

这些接口的意义是把 Python 里的模型调用能力变成 HTTP 服务。

前端或其他后端服务可以通过接口调用,而不是直接依赖 Python 对象。

同步和异步请求

课程里对比了 Python 里的同步请求和异步请求。

异步请求通常会使用异步 HTTP 客户端,并配合 async with 管理连接和响应。

这样请求完成后,相关资源会被正确关闭。

同步请求则更直接。

调用函数后,当前线程会等待结果返回。

这种写法简单,但在高并发后端里容易造成阻塞。

FastAPI 是异步框架

FastAPI 支持异步接口。

但这不代表你在里面写任何代码都不会阻塞。

如果你在异步接口里调用一个耗时的同步函数,它仍然可能卡住当前服务。

例如:

  • 同步网络请求;
  • 同步数据库访问;
  • 同步文件读写;
  • 耗时 CPU 计算;
  • 某些第三方库只提供同步 API。

所以异步框架不是万能保护。

你写进去的代码仍然要区分同步和异步。

为什么 AI 后端会遇到同步调用

很多 AI 相关库并不是所有 API 都有异步版本。

课程里提到,后面可能会用到一些库或平台,它们只提供同步调用。

例如某些插件、某些模型 SDK、某些文档处理库。

如果直接把这些同步调用放进 FastAPI 异步接口里,其他请求可能会被影响。

所以需要理解如何把同步任务隔离出去。

阻塞现象

课程里用接口演示了阻塞。

一个接口执行同步耗时任务时,另一个本应立即响应的接口也会变慢。

这说明同步任务占住了服务处理能力。

如果你只是看代码里写了 async def,就以为一定不会阻塞,这是很危险的误解。

真正要看的是函数内部有没有耗时同步操作。

死循环的问题

异步接口也可能被死循环卡住。

如果代码一直占用 CPU,不把执行权交回事件循环,服务仍然无法处理其他请求。

这和前端事件循环很像。

前端里一个死循环会卡住页面。

后端异步服务里,一个死循环也会让服务无法继续调度任务。

所以不要把“异步”理解成“永远不会卡”。

用线程处理同步任务

解决同步阻塞的一种方式,是把同步任务丢到线程里执行。

这样主事件循环不用一直等待同步函数。

课程里把同步调用改成在线程中运行,让接口能够继续响应。

这个思路适合:

  • 第三方库只有同步 API;
  • 任务主要是等待 IO;
  • 不希望阻塞整个 FastAPI 服务。

但线程不是无限开。

线程也会占资源,线程之间共享变量时还要考虑并发安全。

worker 和线程不是一回事

课程里也区分了 worker 和线程。

Uvicorn worker 通常指进程级别的服务实例。

这里把同步任务丢出去执行,更多是线程层面的处理。

它们都能提高服务承载能力,但解决的问题不同。

不要把“开 worker”和“把同步任务放到线程里”混为一谈。

共享变量与加锁

多个线程可以访问同一个变量。

但如果多个线程同时读写同一个资源,就可能出现竞争问题。

这时需要锁来保护共享资源。

例如只有拿到锁的线程才能修改某个变量。

这和前面数据库并发更新里讲的锁是同一类思想:并发不是不能做,而是要知道谁在什么时候能改同一份数据。

阶段重点

这一节的核心是建立后端异步的真实理解。

LangServe 能把模型能力暴露成接口,但接口能不能稳定服务用户,还取决于你是否正确处理同步阻塞、线程、事件循环和资源竞争。

后面做 AI Agent 后端时,这些问题会反复出现。

AI Agent 课程学习文档。