
Swoole协程sleep导致死锁的根本原因
在Swoole协程中使用Swoole\Coroutine\System::sleep()可能导致“[fatal error]: all coroutines (count: 1) are asleep - deadlock!”错误。 这并非sleep()本身的问题,而是由于Swoole协程调度器和代码执行顺序的组合导致的死锁。
以下代码片段阐述了问题:
true]);
go(function () {
for (;;) {
Swoole\Coroutine\System::sleep(1);
var_dump('dd');
}
});
});
$t->start();
}
}
$proc = new Process(function () {
swoole_async_set(['enable_coroutine' => false]);
$cls = new DeadLock();
Swoole\Timer::after(1000, function () use ($cls) {
$cls->startProcess(); // 关键点:定时器内启动协程
});
});
$proc->start();
死锁原因分析:
-
主进程上下文(
$proc): 主进程显式禁用协程 (swoole_async_set(['enable_coroutine' => false]))。 它仅使用定时器,定时器回调函数中启动一个新的进程。 -
子进程上下文(
$t):DeadLock::startProcess()创建一个新的进程 ($t),并在该进程中启用协程。 这个子进程内只有一个协程,它无限循环调用Swoole\Coroutine\System::sleep(1)。 -
定时器回调与协程调度: 关键在于定时器回调函数
Swoole\Timer::after(1000, ...)。 它在主进程(无协程)的定时器中启动子进程。 当子进程启动后,其内部的协程立即进入睡眠状态。 由于主进程没有协程,Swoole协程调度器无法感知到子进程中的协程,从而认为所有协程都处于睡眠状态,导致死锁。
解决方法:
避免死锁的关键在于确保至少有一个协程始终处于活动状态,或者在主进程中也启用协程。 以下是一些解决方法:
-
在主进程中启用协程: 移除主进程中
swoole_async_set(['enable_coroutine' => false])这行代码,允许主进程也使用协程,从而避免调度器误判。 -
在子进程中添加一个非阻塞协程: 在子进程中添加一个额外的协程,该协程不调用
sleep(),例如一个简单的循环或监听事件的协程,以保持至少一个协程处于活动状态。 -
使用更合适的异步操作: 如果可能,避免使用
sleep(),而是使用Swoole提供的其他异步IO操作,例如Swoole\Coroutine\WaitGroup来协调协程的执行。
总而言之,此死锁并非sleep()本身的错误,而是由于Swoole协程调度器在缺乏上下文信息的情况下,错误地判断所有协程都处于睡眠状态,从而导致的死锁。 通过调整代码结构,确保至少有一个协程处于活动状态,可以有效避免这个问题。










