协程启动无开销,真正瓶颈在于db连接池获取和上下文泄漏;需合理配置pool.max_connections、复用连接、配对context::set/del以避免oom。

协程本身没有创建和销毁开销——Hyperf 里根本不存在“创建协程”这个动作,go 函数只是调度器安排一次协程执行,底层复用的是 Swoole 的协程栈。真正有开销的,是协程内触发的 I/O 操作(比如数据库查询、HTTP 请求)所依赖的资源初始化与上下文管理。
为什么 go 不等于 “新建协程”
很多人误以为每调用一次 go 就像 new Thread() 那样分配内存、初始化栈。实际上,在 Swoole 4.4+(Hyperf 默认要求)中,协程是轻量级执行单元,由协程调度器统一管理固定大小的栈空间(默认 256KB,可配),协程启动只是把函数压入调度队列,复用已有栈帧。
-
go调用本身耗时在纳秒级,几乎可忽略 - 真正可观测的开销来自协程内首次访问
Context::get()、DB::getConnection()或Container::get() - 若协程内只做纯计算(无 I/O、无容器解析),10 万次
go启动耗时仍低于 1ms
DB::getConnection() 是性能关键路径
高并发下最常被卡住的地方不是协程启动,而是连接池取连接。每次 DB::getConnection('default') 会先查协程上下文缓存,没命中才去连接池 get(),而后者在 max_connections 耗尽时会阻塞等待 wait_timeout(默认 3 秒)。
- 连接池空闲连接数不足时,
getConnection()可能成为瓶颈,而非go - 务必检查
config/autoload/database.php中的pool.max_connections是否 ≥ 并发峰值 × 1.5(非线性增长) - 避免在循环内高频调用
DB::getConnection(),应复用已获取的$connection实例 - 使用 Eloquent 时,
User::query()->get()内部已自动复用连接,无需手动干预
协程上下文泄漏导致内存缓慢增长
协程退出后,若往 Context::set() 存了未清理的对象(如 PDO 实例、大数组、闭包),这些数据不会自动释放,积累多了引发 OOM。这不是协程销毁慢,而是开发者忘了清理。
- 所有
Context::set($key, $value)都应配对Context::del($key),尤其在中间件或装饰器中 - 避免把整个
Container或DbPool实例塞进上下文 - 用
co::stats()查看当前协程总数和内存占用,配合memory_get_usage(true)定位泄漏点 - Hyperf 3.2+ 提供
hyperf/memory-leak-detector组件,可自动扫描长期驻留的上下文键
协程性能问题从来不在“启停”,而在资源绑定与上下文生命周期管理。盯住 getConnection 和 Context::set 这两个点,比优化 go 调用频次有效十倍。











