Skip to content

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

学习目标 ​

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

学完后,你应该能理解:

  • LangServe 会为 runnable 暴露哪些接口;
  • invoke、batch、stream 与异步方法是什么关系;
  • 同步 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 课程学习文档。