您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“PHP怎么實現(xiàn)平滑關(guān)閉和重啟”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
要實現(xiàn)平滑關(guān)閉/重啟不難,這里先講解兩個知識點:
當(dāng)我們的程序正在處理一個任務(wù)的時候,你肯定不希望它中途被終止,比如說你在執(zhí)行一個數(shù)據(jù)庫事務(wù),肯定不希望事務(wù)還沒被提交進(jìn)程就被終止了。
<?php echo "開始執(zhí)行事務(wù)" . PHP_EOL; // 模擬一些耗時的操作 $finish_time = time() + 5; while (time() < $finish_time) { } echo "事務(wù)執(zhí)行完畢" . PHP_EOL;
上面這段代碼,如果你在第二個 echo 之前用 kill 命令去殺死這個進(jìn)程,那么第二個 echo 就不會被執(zhí)行了。那能不能做到在事務(wù)過程中暫時先忽略 kill 信號呢?
能。我們可以使用 pcntl_sigprocmask() 來阻塞信號,讓事務(wù)完成之后再響應(yīng) kill 信號。
<?php // 阻塞信號 $sig_set = array(SIGINT, SIGTERM); // 要阻塞的信號集合 pcntl_sigprocmask(SIG_BLOCK, $sig_set); // SIG_BLOCK: 把信號加入到當(dāng)前阻塞信號中 echo date("[Y-m-d H:i:s]") . " 開始執(zhí)行事務(wù)" . PHP_EOL; $finish_time = time() + 5; while (time() < $finish_time) { } echo date("[Y-m-d H:i:s]") . "事務(wù)執(zhí)行完畢" . PHP_EOL; pcntl_sigprocmask(SIG_UNBLOCK, $sig_set); // SIG_UNBLOCK: 從當(dāng)前阻塞信號中移出信號
同樣的,在第二個 echo 之前按下 Ctrl + C 或者用 kill 命令去殺這個進(jìn)程,你會發(fā)現(xiàn)第二個 echo 正常執(zhí)行了,并且兩條輸出的時間間隔是 5 秒。
我們的常駐進(jìn)程通常是在一個 while(true) 循環(huán)中去執(zhí)行重復(fù)的任務(wù),如果這么寫的話:
<?php while (true) { pcntl_sigprocmask(SIG_BLOCK, $sig_set); // ... pcntl_sigprocmask(SIG_UNBLOCK, $sig_set); }
我們是可以保證一個事務(wù)不會被打斷,但是我們的程序還不知道是不是已經(jīng)接收到信號了,并且把阻塞信號移除之后進(jìn)程立刻就退出了,沒辦法去做一些收尾工作(比如關(guān)閉文件)。
為了解決上面提到的問題,我們需要在信號發(fā)生的時候去做收尾工作,然后再退出進(jìn)程。
pcntl 擴(kuò)展提供了一些信號相關(guān)的函數(shù),我們可以使用 pcntl_signal() 和 pcntl_signal_dispatch() 來注冊信號處理器和分發(fā)信號。
<?php $sig_handler = function ($signo) { echo "收到信號 {$signo}" . PHP_EOL; }; pcntl_signal(SIGINT, $sig_handler); // 給 SIGINT 信號注冊一個處理器 // 模擬耗時操作 echo "開始執(zhí)行事務(wù)" . PHP_EOL; $finish_time = time() + 5; while(true) { if (time() > $finish_time) { echo "事務(wù)執(zhí)行完畢" . PHP_EOL; break; } } pcntl_signal_dispatch(); // 分發(fā)信號
執(zhí)行上面這段代碼并在 5 秒內(nèi)按下 Ctrl + C,你會看到 sig_handler 被執(zhí)行了;而如果不按下 Ctrl + C,那么 sig_handler 就不會被執(zhí)行。
到這里你應(yīng)該已經(jīng)理解了 pcntl_signal() 和 pcntl_signal_dispatch() 的用法了,把它放到到剛剛的代碼試試
<?php $sig_handler = function ($signo) { echo "收到信號 {$signo}" . PHP_EOL; }; $sig_set = array(SIGINT, SIGTERM); foreach ($sig_set as $sig) { pcntl_signal($sig, $sig_handler); // 注冊多個信號 } // [1] while (true) { // [2-1] pcntl_sigprocmask(SIG_BLOCK, $sig_set); // [2-2] // ... // [2-3] pcntl_sigprocmask(SIG_UNBLOCK, $sig_set); // [2-4] } // [3]
pcntl_signal_dispatch() 該放哪里呢?是 [1] [2] 還是 [3]?先動手試一下
然后你會發(fā)現(xiàn),只有放在 [2] 才能讓信號處理器執(zhí)行。同時這個實驗也告訴我們 pcntl_signal_dispatch() 要在信號發(fā)生后才會使處理器執(zhí)行:放在 [1] 時,除非你手速足夠快,不然在你按下 Ctrl + C 或者是 kill 之前就已經(jīng)執(zhí)行過了;而放在 [3] 它就永遠(yuǎn)沒機(jī)會執(zhí)行。
至于放在 [2] 的哪個位置,我建議是放在 [2-4],因為這個時候已經(jīng)處理完任務(wù)了。
到這里你已經(jīng)了解平滑關(guān)閉/重啟的原理了,我們把上面的半成品代碼(因為在收到信號后可能還會進(jìn)入下一層循環(huán))整理一下:
<?php $running = true; $sig_handler = function ($signo) use (&$running) { echo "收到信號 {$signo}" . PHP_EOL; // 做收尾工作 $running = false; }; $sig_set = array(SIGINT, SIGTERM, SIGUSR2 /* 熟悉的 USR2 信號不能漏 */); foreach ($sig_set as $sig) { pcntl_signal($sig, $sig_handler); // 注冊多個信號 } while ($running) { pcntl_sigprocmask(SIG_BLOCK, $sig_set); // ... 業(yè)務(wù)邏輯 pcntl_sigprocmask(SIG_UNBLOCK, $sig_set); pcntl_signal_dispatch(); }
我們就得到了一個可以平滑程序的常駐進(jìn)程框架,你也可以把它封裝成一個類。
“PHP怎么實現(xiàn)平滑關(guān)閉和重啟”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。