
onWorkerStart 是 Workerman 中用于在 Worker 进程启动后执行初始化逻辑的重要回调。尽管从 v3 到 v5 它的回调签名一直保持为接受一个 Worker 对象作为参数,但不同版本中它的执行时机、可访问的全局状态以及与事件循环的配合方式已经发生了明显变化。这些差异会直接影响数据库连接、缓存实例、定时器和协程客户端的初始化位置。本文将重点对比 v3 到 v5 在 onWorkerStart 上的关键变化,并给出迁移建议。
一、回调签名与触发时机的变化
从 v3 到 v5,onWorkerStart 的基本回调形式没有改变,仍然可以写作 function($worker) 或者更严格的 function(Workerman\Worker $worker)。参数中传入的 Worker 对象在回调触发时已经完成了基本的进程绑定,可以通过 $worker->id 获取当前 Worker 进程的编号。这个编号从 0 开始,不同 Worker 实例在启动时都会从 0 开始重新编号,因此它并不是全局唯一的进程 ID,更多用于日志标识和按进程分组处理。
真正发生变化的是回调触发前后的内部状态准备。在 v3 中,onWorkerStart 执行时全局事件循环通常已经可以被访问,部分开发者习惯在回调中直接通过 Worker::$globalEvent 获取事件循环对象。到了 v4 和 v5,事件循环被逐步抽象到独立的 EventLoop 层,Worker::$globalEvent 这类直接暴露的属性不再推荐使用,甚至在 v5 中已经移除。如果你的旧代码仍然引用这些属性,升级后会直接出现未定义属性或类型错误。因此,即便回调签名看起来没变,实际可用的运行上下文已经不同。
另一个需要留意的是进程 fork 时机。onWorkerStart 必须在子进程 fork 完成之后触发,这样才能保证连接、缓存等资源不会在父进程中被复制出多个错误句柄。v3 和 v5 都遵循这一原则,但 v5 在 fork 后还会初始化协程调度环境,所以如果你的回调里有依赖协程上下文的操作,执行顺序会略微靠后。这个细微差异在阻塞式 PHP 代码中不容易察觉,一旦切换到协程客户端就会暴露出来。
二、v3 写法迁移到 v5 时的常见失效点
很多从 v3 直接升级到 v5 的项目会保留原来的全局变量初始化写法。例如在 onWorkerStart 外层先创建一个 $db 连接,然后在回调内直接赋值给 global $db。这种写法在 v3 的纯阻塞模型下偶尔能够运行,因为子进程会复制父进程的连接句柄,短连接场景下表现尚可。但在 v5 中,由于事件循环和协程调度对连接生命周期管理更严格,继承来的连接句柄很容易在第一次网络 IO 时失效,表现为连接已断开或数据错乱。
下面是一段 v3 风格的常见写法,它依赖全局变量在回调内外共享:
<?php
use Workerman\Worker;
$worker = new Worker('tcp://0.0.0.0:2345');
$worker->count = 4;
$worker->onWorkerStart = function($worker) {
global $db;
$db = new PDO('mysql:host=127.0.0.1;dbname=test', 'root', 'root');
echo "worker " . $worker->id . " started\n";
};
Worker::runAll();
这种写法的问题在于 global $db 只在当前进程内有效,而且如果外层已经存在同名变量,会被 fork 复制到子进程。v5 推荐把连接初始化完全封闭在 onWorkerStart 内部,并通过 $worker 对象属性或静态变量持有连接,而不是依赖全局作用域。这样每个子进程都能获得独立且有效的连接资源,避免跨进程共享带来的诡异问题。
另一个失效点是旧代码中使用 Worker::$globalEvent 手动添加事件。v3 中这种写法可以绕过框架直接在底层事件循环上挂载监听器。v5 中该属性已经不再提供,继续使用会触发错误。例如下面的写法在 v3 可能可用,但在 v5 中会直接失败:
<?php
$worker->onWorkerStart = function($worker) {
$loop = Worker::$globalEvent;
$loop->add($fd, \Workerman\Events\EventInterface::EV_READ, function() {
// v3 可用的底层事件注册方式
});
};
迁移时需要改成通过 Workerman 提供的定时器、连接接口或事件循环抽象层来完成类似功能,而不是访问已移除的静态属性。这个变化往往不会在启动时报错,而是在具体的 IO 回调触发时才暴露,排查起来比较耗时。
三、v5 中推荐的初始化模式
在 v5 中,更稳妥的做法是利用静态变量在 onWorkerStart 内部做进程级单例初始化。因为每个子进程的内存空间是独立的,静态变量在单个进程内只会初始化一次,正好满足数据库连接、Redis 客户端等长连接资源的创建需求。下面的示例展示了这种写法:
<?php
use Workerman\Worker;
$worker = new Worker('tcp://0.0.0.0:2345');
$worker->count = 4;
$worker->onWorkerStart = function($worker) {
static $db = null;
if ($db === null) {
$db = new PDO('mysql:host=127.0.0.1;dbname=test', 'root', 'root');
}
$worker->db = $db;
};
Worker::runAll();
这段代码把连接创建限制在回调内,并用静态变量避免重复连接。由于每个 worker 进程都会执行一次 onWorkerStart,因此每个进程都会持有自己的 PDO 实例。即使 $worker->count 设置成 4,也会创建 4 个独立连接,这符合 Workerman 的多进程模型。需要注意连接数会随 worker 数量线性增长,生产环境中应合理设置 count 并配合连接池管理。
如果 v5 项目中使用协程客户端,初始化逻辑还需要放到协程上下文中。例如 Workerman\Coroutine 提供的协程创建方法,可以把异步连接初始化包装成协程任务,避免阻塞事件循环。与 v3 不同的是,v5 的 onWorkerStart 回调执行时事件循环尚未进入正式调度阶段,如果在这里直接发起阻塞式网络请求,会拖慢整个 worker 的启动速度。因此建议将耗时初始化拆分为协程任务,或者使用连接池的预创建接口。
四、迁移排查清单
从 v3 升级到 v5 时,可以按照下面的清单逐项检查 onWorkerStart 相关代码,降低迁移风险。
- 确认所有数据库连接、Redis 客户端、缓存实例都在 onWorkerStart 内部初始化,不要在 Worker 外层创建。
- 检查是否使用了
Worker::$globalEvent,如果有,改为官方事件循环或定时器接口。 - 检查是否依赖
global关键字在多个回调之间传递连接,改用$worker属性或静态变量。 - 确认
$worker->onWorkerStart回调中不会执行阻塞式网络调用,尤其是在 v5 的协程模式下。 - 关注 worker count 与连接数的比例,避免初始化过多空闲连接占用数据库或缓存资源。
通过上述调整,大部分 v3 项目可以在不改动业务逻辑的情况下平滑迁移到 v5。onWorkerStart 本身仍然是初始化资源的正确位置,但运行环境的升级要求我们对进程隔离和协程调度有更清晰的理解。只有把初始化代码控制在回调内部,并避免依赖已经移除的底层属性,才能真正发挥 v5 在性能和协程支持上的优势。
onWorkerStartWorkerman版本差异回调函数变化修改时间:2026-08-25 10:02:31