aiohttp(Python) vs PHP 异步请求 全方位对比
aiohttp(Python) vs PHP 异步请求 全方位对比
你有PHP开发底子,我先把两套语言的同步阻塞模型、异步底层原理、代码写法、坑点、使用场景一一对照,打通思维差异;
先梳理核心前提:PHP主流异步方案就2种:Swoole / Workerman,原生PHP完全没有异步。
Python:原生语言就内置 async/await 协程 + asyncio事件循环,aiohttp基于这套体系。
一、底层核心机制对比(最关键,弄懂就通透)
1)传统PHP(LNMP 普通php-fpm):完全同步阻塞
- 每来1个请求,php-fpm 开启一个进程处理;
- 调用curl请求第三方接口、网络IO、sleep等待时:整个进程卡住休眠,CPU无事可做;
- 并发高了只能不断加进程,资源开销巨大;
- 无法实现单进程内并发发起多个HTTP请求。
2)PHP异步:Swoole/Workerman 协程(PHP唯一异步方案)
Swoole 提供:协程+事件循环,底层epoll多路复用,和Python asyncio设计思路几乎一模一样
- 单进程依托事件循环,IO等待时让出CPU,去执行其他任务;
- curl换成
Swoole\Coroutine\Http协程客户端,就是PHP版aiohttp;
3)Python aiohttp + asyncio
Python 语言原生支持协程语法 async/await,asyncio是事件循环调度器
aiohttp = 基于asyncio事件循环的异步HTTP客户端
逻辑和Swoole协程1:1对标:
IO阻塞(等待接口返回)→ 交出控制权 → 事件循环调度其他协程运行
一句话对应关系
| Python 概念 | PHP Swoole 对应概念 |
|---|---|
| async def 异步函数 | function 协程函数 |
| await | yield/协程挂起(Swoole自动封装无需手动写) |
| asyncio 事件循环 | Swoole EventLoop |
| aiohttp.ClientSession | Swoole\Coroutine\Http\Client |
| asyncio.gather() | Co::multi() 并发批量请求 |
二、基础语法对照:单次POST请求(调用Ollama接口)
版本1:PHP 同步 curl(最常用、阻塞)
<?php
$url = "http://127.0.0.1:11434/api/chat";
$postData = json_encode([
"model" => "llama3:8b",
"messages" => [["role"=>"user","content"=>"什么是RAG"]],
"stream" => false
]);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Content-Type: application/json"]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
// 阻塞:程序卡死在这里等待接口返回
$res = curl_exec($ch);
curl_close($ch);
$data = json_decode($res, true);
echo $data["message"]["content"];
特点:等待期间整个脚本停滞,不能干别的。
版本2:PHP Swoole 协程异步请求(非阻塞)
<?php
Co\run(function () {
$cli = new Swoole\Coroutine\Http\Client('127.0.0.1', 11434);
$cli->set(['timeout' => 30]);
$postData = json_encode([
"model" => "llama3:8b",
"messages" => [["role"=>"user","content"=>"什么是RAG"]],
"stream" => false
]);
$cli->post("/api/chat", $postData, ["Content-Type" => "application/json"]);
// IO等待时,CPU可以去执行其他协程,不会阻塞进程
$result = json_decode($cli->body, true);
var_dump($result["message"]["content"]);
});
版本3:Python aiohttp 等价写法
import aiohttp
import asyncio
async def request_llm():
url = "http://127.0.0.1:11434/api/chat"
payload = {
"model": "llama3:8b",
"messages": [{"role":"user","content":"什么是RAG"}],
"stream": False
}
async with aiohttp.ClientSession() as session:
async with session.post(url, json=payload, timeout=aiohttp.ClientTimeout(total=30)) as resp:
res = await resp.json()
print(res["message"]["content"])
# 启动事件循环,运行协程函数
asyncio.run(request_llm())
三、核心关键字逐行对照(最难理解的 await、async)
async def
PHP:Swoole中普通函数放在Co\run()内部自动变成协程函数,不用标记关键字;
Python:必须手动给函数加async标识,代表这是一个协程函数,普通代码无法直接调用。await
作用:遇到网络IO,挂起当前协程,让出事件循环控制权
- PHP Swoole:内置封装好了,调用协程Http客户端时自动挂起,不用写任何关键字;
- Python:但凡IO操作(发请求、sleep、读文件)前面必须手写await,否则报错。
- async with
PHP里没有异步上下文管理器概念;
Python:异步资源(Session、链接)需要异步自动关闭,等价手动close()销毁客户端连接。
四、并发批量请求对比(最能体现异步优势)
需求:同时发起3个LLM提问,串行很慢,异步并发瞬间跑完
PHP Swoole 多协程并发
<?php
Co\run(function () {
$prompts = [
"什么是向量嵌入",
"Chroma和FAISS区别",
"Llama3部署难点"
];
$tasks = [];
foreach ($prompts as $p) {
$tasks[] = Co::create(function () use ($p) {
$cli = new Swoole\Coroutine\Http\Client('127.0.0.1', 11434);
$cli->post("/api/chat", json_encode([
"model"=>"llama3:8b",
"messages"=>[["user", $p]]
]));
return [$p, json_decode($cli->body, true)["message"]["content"]];
});
}
// 等待所有协程全部执行完毕
$results = Co::wait($tasks);
var_dump($results);
});
Python asyncio.gather 并发(完全对等)
import aiohttp, asyncio
async def single_task(session, prompt):
payload = {"model":"llama3:8b","messages":[{"role":"user","content":prompt}]}
async with session.post("http://127.0.0.1:11434/api/chat", json=payload) as resp:
data = await resp.json()
return prompt, data["message"]["content"]
async def batch_request():
prompts = ["什么是向量嵌入","Chroma和FAISS区别","Llama3部署难点"]
async with aiohttp.ClientSession() as session:
tasks = [single_task(session, p) for p in prompts]
# 并发执行所有任务,收集结果 = Co::wait()
results = await asyncio.gather(*tasks)
return results
asyncio.run(batch_request())
五、连接池复用 对比(性能重点)
PHP Swoole
推荐全局实例化一个Http客户端连接池,复用TCP链接,避免反复创建销毁连接开销;
Python aiohttp
核心铁律:全局只创建1个 ClientSession 对象全程复用
千万不要每次请求都新建Session(和反复new HttpClient一个道理,损耗极大)
✅正确写法:全局会话
# 全局单例session
session: aiohttp.ClientSession | None = None
async def get_session():
global session
if session is None:
session = aiohttp.ClientSession()
return session
六、异常捕获对照
PHP Swoole 异常
try {
$cli->post(...);
} catch (\Swoole\Coroutine\Http\Exception $e) {
// 连接失败、超时
}
Python aiohttp 常用异常
from aiohttp import ClientConnectorError, ClientResponseError
try:
async with session.post(...) as resp:
resp.raise_for_status() # 非200抛出异常
except ClientConnectorError:
print("服务未启动,连接失败")
except asyncio.TimeoutError:
print("请求超时")
except Exception as e:
print(e)
七、FastAPI + aiohttp ≈ PHP Swoole Http服务
这是你当下最贴合的场景:
- FastAPI:基于ASGI异步事件循环的Web框架
等价:Swoole Http服务 - 接口内部使用 aiohttp 调用大模型接口
等价:Swoole协程Http客户端 - 整条链路全程异步无阻塞,大量用户同时问答不会卡顿
传统PHP-FPM做不到这点,这也是Python做AI后端碾压PHP的核心原因。
八、PHP开发者最容易踩的Python异步坑(逐个拆解)
坑1:同步函数里不能调用await/aiohttp
PHP:随便在哪都可以调用协程客户端;
Python:await 只能写在 async def 函数内部,普通def同步函数直接调用会报错卡死。
坑2:分不清同步requests 和异步aiohttp
requests = PHP curl(同步阻塞)
aiohttp = PHP Swoole协程客户端(异步非阻塞)
写RAG、LLM并发接口一律只用aiohttp。
坑3:频繁创建ClientSession
等同于PHP循环里不停new HttpClient,TCP频繁握手,性能暴跌。
坑4:忘记写await
Python调用异步方法不加await,拿到的只是协程对象,不会真正执行请求。
坑5:事件循环概念模糊
PHP开发者平时感知不到事件循环;
Python所有异步代码都跑在asyncio事件循环里,FastAPI内部自动维护事件循环,不用手动启动。
九、简明对照表汇总
| 功能 | PHP(Swoole协程) | Python(asyncio+aiohttp) |
|---|---|---|
| 异步函数标记 | 无需标记 | async def |
| IO挂起等待 | 自动协程切换 | await 关键字 |
| HTTP客户端 | Swoole\Coroutine\Http\Client | aiohttp.ClientSession |
| 并发批量请求 | Co::multi() / Co::wait() | asyncio.gather() |
| 超时设置 | $cli->set([‘timeout’=>30]) | ClientTimeout |
| 上下文自动关闭 | 手动close() | async with 自动释放 |
| Web异步服务 | Swoole HttpServer | FastAPI |
十、给你的学习建议
- 记住:Python异步 = Swoole协程,思维完全互通;
- 写对接Ollama的接口统一套路:
FastAPI异步接口 → 全局复用aiohttp会话 → await发起请求 → 返回结果/流式输出 - 先熟练写单次请求 → 再练并发请求 → 最后封装通用LLM请求工具类。
需要我把你之前写的PHP调用大模型代码,逐行翻译成Python aiohttp版本给你对照看吗?
本作品采用《CC 协议》,转载必须注明作者和本文链接
关于 LearnKu