Workerman子进程中调用app()->make()会卡住,因TP6容器依赖HTTP请求上下文,而Workerman常驻内存、无请求生命周期,导致中间件注册、配置加载、数据库连接池初始化等操作因静态状态残留(如未释放PDO连接、挂起协程)而阻塞。

Workerman子进程里调用app()->make()为什么会卡住?
因为TP6的容器初始化依赖HTTP请求上下文,而Workerman是常驻进程、无请求生命周期。app()->make()内部可能触发中间件注册、配置加载、甚至数据库连接池初始化——这些操作在FPM下由每次请求自动重置,在Workerman里却只执行一次,且后续调用会因静态状态残留(比如未完成的锁、挂起的协程、未释放的PDO连接)而阻塞。
think\App在onWorkerStart中new一次就出问题?
不是“出问题”,是直接破坏进程稳定性。TP6的think\App类设计时假设自己运行在单次请求内:它会绑定全局事件监听器、初始化日志驱动、注册错误处理器——这些在Workerman多子进程中重复执行,会导致:
- 多个子进程往同一个日志文件并发写入,触发
flock争抢或file_put_contents(..., FILE_APPEND)非原子覆盖 - 数据库连接池被各子进程独立初始化,但若共用同一MySQL连接配置,可能触发
MySQL server has gone away - Session/Cache驱动若基于文件或Redis且未做进程隔离,不同Worker间会互相污染数据
为什么vendor/autoload.php引入后仍无法安全使用Service?
自动加载只是解决“类找不到”,不解决“类不能用”。TP6的Service类往往隐式依赖以下FPM特有机制:
- 请求级单例(如
Request、Response对象),在长连接中复用会导致状态错乱 - 中间件栈在
App::run()中动态组装,Workerman没走这个流程,middleware注解或配置不会生效 - 数据库事务管理器默认绑定到当前请求,跨请求复用连接时,事务未提交/回滚就进入下一个“伪请求”
所以即使app()->make(UserService::class)能返回实例,只要它内部调用了Db::transaction()或cache()->get(),大概率在第二次调用时卡死或返回脏数据。
真正安全的Service复用方式是什么?
绕过TP6容器,手动构造无状态Service实例,并切断对TP核心组件的依赖:
- Service类避免继承
think\Service,不调用app()、config()、db()等全局函数 - 数据库操作改用原生PDO或Swoole\Coroutine\MySQL,连接由Worker进程自行维护
- 缓存改用
Redis::connect()直连,key加前缀区分Worker ID(如worker_123:user:1001) - 必须用TP模型时,显式传入干净的
Db实例:new User(['connection' => new \think\db\Connection($config)])
复杂点在于:TP6的Service天然为FPM设计,强行塞进Workerman就像把汽车引擎装进自行车——能转,但链条随时会崩。最稳的做法,是把业务逻辑抽成纯PHP类,彻底脱离TP容器生命周期约束。











