POST://python-asyncio-intro
带点橘子味的馒头的头像
带点橘子味的馒头

FRONTEND / DESKTOP DEV

返回文章列表

Python 异步编程入门:从同步到 asyncio 的思维转换

1 分钟· 778 85 次阅读

做 LAN 聊天系统的 WebSocket 服务端时,第一次真正需要 Python 异步。这篇从思维转换的角度,讲清楚 asyncio 的核心。

同步代码为什么"卡"

同步的 HTTP 服务器处理两个请求时,第一个没完成第二个就得等——这叫阻塞。阻塞发生在哪里?主要在 I/O 等待:读文件、网络收发、数据库查询。

# 同步:等 resp 期间啥也干不了
resp = requests.get("https://api.example.com")

协程:让出控制权

async/await 的本质是协程让出:执行到 await 时,把控制权还给事件循环,等 I/O 完成再回来继续:

import asyncio

async def fetch(url):
    print(f"开始 {url}")
    await asyncio.sleep(1)   # 模拟 I/O,让出控制权
    print(f"完成 {url}")
    return url

async def main():
    # 并发执行三个任务
    results = await asyncio.gather(
        fetch("a.com"), fetch("b.com"), fetch("c.com"),
    )
    print(results)

asyncio.run(main())
# 总耗时约 1 秒,而不是 3 秒

关键理解:await asyncio.sleep(1) 不是"睡 1 秒",而是"告诉事件循环:我暂停 1 秒,你先去跑别的"。

事件循环:调度器

asyncio.run() 启动一个事件循环,它维护一个任务队列,不断执行就绪的协程。这就是"单线程并发"的秘密——不是并行,而是协同式调度

真实场景:WebSocket 服务端

聊天系统的核心需求是"多个客户端同时连接,各自收发消息"。用异步最合适:

import asyncio
import websockets

async def handler(ws, path):
    async for message in ws:      # 等待消息时让出控制权
        await ws.send(f"echo: {message}")  # 发送也异步

async def main():
    async with websockets.serve(handler, "0.0.0.0", 8765):
        await asyncio.Future()    # 永远运行

asyncio.run(main())

一个连接阻塞在 async for 等待消息时,其他连接照样收发——这就是异步的价值。

心跳与并发写:两个真实坑

坑一:假死连接

长时间不通信,NAT 会静默断开 TCP,两端都不知道。要主动发心跳:

async def heartbeat(ws):
    while True:
        await asyncio.sleep(30)
        try:
            await ws.send('{"type":"ping"}')
        except Exception:
            break  # 连接已死

坑二:并发写同一个连接

多个协程同时 ws.send 会竞争底层连接。用锁串行化:

class Connection:
    def __init__(self, ws):
        self.ws = ws
        self._lock = asyncio.Lock()

    async def send(self, data):
        async with self._lock:
            await self.ws.send(data)

同步代码混入异步:最大的坑

在协程里调用同步阻塞函数,会把整个事件循环卡死:

# ❌ 同步 requests.get 阻塞事件循环,所有连接都卡住
async def handler(ws):
    data = requests.get("https://api.example.com")  # 卡!

解法:用 asyncio.to_thread 丢到线程池,或用异步版库(httpx.AsyncClient):

async def handler(ws):
    data = await asyncio.to_thread(requests.get, "https://api.example.com")

规则:协程里只能用异步 I/O,同步阻塞操作必须丢线程池。

小结

asyncio 的思维转换三步:

  1. 理解 await = 让出控制权,不是"等结果";
  2. 理解事件循环 = 协作式调度器,单线程并发;
  3. 牢记铁律:协程里不跑阻塞代码

想清楚这三件事,异步编程就从"魔法"变成了"常规工具"。

相关文章