async def main():
    sem = asyncio.Semaphore(10)
    await asyncio.gather(*[worker(sem, u) for u in urls])

在模块顶层写了 sem = asyncio.Semaphore(10),导入时不报错,进入协程 acquire 时才抛 RuntimeError,提示 got Future attached to a different loop,服务一启动就全挂,重试也没用,换个入口也一样。先别急着动参数,把报错原文和时间点记下来,顺序反了容易白折腾。

原因不复杂,Semaphore 内部会绑定首次使用时的循环,模块导入时还没有运行中的循环,等真正跑起来两者对不上,等待队列挂到了另一个循环上。

「Semaphore 绑定循环」排查到最后往往是很小的一处差异,前面那些复杂猜测可以先放一放。备份不是备了就行,恢复一次才算数,很多备份文件打开才发现是空的。

接口问题先分清是没发出去、发出去了没收到,还是收到了处理失败。

处理上按这个顺序:信号量放到协程启动之后再创建,跟着当前循环走,别放在模块顶层;需要全局共享的用启动钩子初始化,别写在模块顶层;升级版本前先查这个对象的绑定行为有没有变。

改完只确认一件事:acquire 不再报循环不匹配,并发数稳定压在十个,任务也没有出现堆积。

作者 9823189