swoole協程sleep導致死鎖的根本原因
在Swoole協程中使用SwooleCoroutineSystem::sleep()可能導致“[fatal Error]: all coroutines (count: 1) are asleep – deadlock!”錯誤。 這并非sleep()本身的問題,而是由于Swoole協程調度器和代碼執行順序的組合導致的死鎖。
以下代碼片段闡述了問題:
<?php use SwooleProcess; class DeadLock { public function startProcess() { $t = new SwooleProcess(function () { swoole_async_set(['enable_coroutine' => true]); go(function () { for (;;) { SwooleCoroutineSystem::sleep(1); var_dump('dd'); } }); }); $t->start(); } } $proc = new Process(function () { swoole_async_set(['enable_coroutine' => false]); $cls = new DeadLock(); SwooleTimer::after(1000, function () use ($cls) { $cls->startProcess(); // 關鍵點:定時器內啟動協程 }); }); $proc->start();
死鎖原因分析:
-
主進程上下文($proc): 主進程顯式禁用協程 (swoole_async_set([‘enable_coroutine’ => false]))。 它僅使用定時器,定時器回調函數中啟動一個新的進程。
-
子進程上下文($t): DeadLock::startProcess() 創建一個新的進程 ($t),并在該進程中啟用協程。 這個子進程內只有一個協程,它無限循環調用 SwooleCoroutineSystem::sleep(1)。
-
定時器回調與協程調度: 關鍵在于定時器回調函數 SwooleTimer::after(1000, …)。 它在主進程(無協程)的定時器中啟動子進程。 當子進程啟動后,其內部的協程立即進入睡眠狀態。 由于主進程沒有協程,Swoole協程調度器無法感知到子進程中的協程,從而認為所有協程都處于睡眠狀態,導致死鎖。
解決方法:
避免死鎖的關鍵在于確保至少有一個協程始終處于活動狀態,或者在主進程中也啟用協程。 以下是一些解決方法:
-
在主進程中啟用協程: 移除主進程中 swoole_async_set([‘enable_coroutine’ => false]) 這行代碼,允許主進程也使用協程,從而避免調度器誤判。
-
在子進程中添加一個非阻塞協程: 在子進程中添加一個額外的協程,該協程不調用 sleep(),例如一個簡單的循環或監聽事件的協程,以保持至少一個協程處于活動狀態。
-
使用更合適的異步操作: 如果可能,避免使用 sleep(),而是使用Swoole提供的其他異步IO操作,例如SwooleCoroutineWaitGroup來協調協程的執行。
總而言之,此死鎖并非sleep()本身的錯誤,而是由于Swoole協程調度器在缺乏上下文信息的情況下,錯誤地判斷所有協程都處于睡眠狀態,從而導致的死鎖。 通過調整代碼結構,確保至少有一個協程處于活動狀態,可以有效避免這個問題。