成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

看看 PHP 高性能框架 Workerman 源碼中信號的使用姿勢

開發 前端
信號是一種輕量級的消息傳遞機制,通常用于軟件中斷異步通知。比如我們常用的 kill -9 xxx? 就是對 xxx 程序發送了強制終止其進程的 SIGKILL 信號,還有我們經常會用的 Ctrl+C 或 Ctrl+D 等鍵盤操作事件,也會產生 SIGINT 終端退出信號。

大家好,我是碼農先森。

信號是一種輕量級的消息傳遞機制,通常用于軟件中斷異步通知。比如我們常用的 kill -9 xxx 就是對 xxx 程序發送了強制終止其進程的 SIGKILL 信號,還有我們經常會用的 Ctrl+C 或 Ctrl+D 等鍵盤操作事件,也會產生 SIGINT 終端退出信號。

但在我們平時的業務編程中很少會涉及到信號,這是由于信號通常都是用在進程的管理上,比如父子進程的通信、進程資源的管理等。因此在 PHP-FPM 模式下是不會用多進程編程的,如果那位大膽的朋友用了,那恭喜你會收獲到意想不到的驚喜。

不過如果經常使用 Workerman 或 Swoole 編程的朋友,那對信號應該頗有了解了吧。這里不管大家有沒有接觸過信號,都建議學習其原理和用法,興許對以后的編程有所幫助,畢竟技術這東西就怕用時方恨少。為了更通俗易懂的描述信號,我用一個大白話的例子來講解一下。假設你是一個打工仔,這里好像不用假設因為你就是個打工仔哈哈,這時你正戴著耳機沉浸在代碼的世界了。

突然公司廣播響起了吃「下午茶」的音樂,于是你便快馬加鞭趕到下午茶的餐桌面前蓄勢待發。才剛吃上雞腿,公司廣播又響起了線上 Bug 的告警聲,你不得不叼著雞腿,又回到座位上排查起問題了。線上問題還沒解決,這是公司廣播又通知你參加某某答辯會,但這次你完全沒有時間顧及這個通知,所以你完全忽略了。

這里的公司你可以理解為操作系統,廣播通知理解為信號,你自己理解為一個進程。你在接收到信號之后,可以做對應的事情,同樣也可以做與通知不相干的事情,比如通知叫你改線上的 Bug 但是你舍不得下午茶還想繼續吃,那你也可以任性不及時改「只不過事后就要挨批了哈哈」。

還有就是也可以把通知不當回事,不予理會完全忽略不處理。這段大白話雖說的好,但中國有句古話「說的好聽不如做的好看」,那么接下來我們就以實際的例子來以身入代碼。

下面這個是當前進程接收外部信號的例子,使用 pcntl_signal 注冊指定信號的回調函數,回調函數 signalHandler 會根據接收到的不同信號進入到相應的 switch 分支中執行對應的處理邏輯。

處理完之后,便可以退出程序。其中的 pcntl_signal_dispatch 函數起著監聽待處理信號,并執行信號所注冊回調函數的作用。還有 where(true) 會一直循環執行,直到接收到信號。

<?php

// 定義信號處理函數
function signalHandler($signal)
{
    switch ($signal) {
        case SIGINT:
            // 比如公司廣播響起了吃 下午茶 的音樂
            echo "捕獲到 SIGINT 信號... " . PHP_EOL;
            // 接下來可以自行實現相應的邏輯
            exit;
        case SIGTERM:
            // 比如公司廣播又響起了線上 Bug 的告警聲
            echo "捕獲到 SIGTER 信號... " . PHP_EOL;
            // 接下來可以自行實現相應的邏輯
            exit;
        case SIGALRM:
            // 比如公司廣播又通知你參加某某答辯會
            echo "捕獲到 SIGALRM 信號... " . PHP_EOL;
            // 接下來可以自行實現相應的邏輯
            // 當然你也可以不處理,任由它去
            exit;
        default:
            echo "捕獲到未知信號: $signal " . PHP_EOL;
            exit;
    }
}

// 注冊信號回調函數
// 就好比接受到不同的通知,就應該要做對應的事情
// 這里統一回調 signalHandler 這個函數,你也可以定義不同的函數
pcntl_signal(SIGINT, 'signalHandler');
pcntl_signal(SIGTERM, 'signalHandler');

// 輸出一下當前的進程號,方便測試
echo "當前進程 PID: " . getmypid() . PHP_EOL;

// 不斷監聽信號,接收到信號后調用相應的信號回調函數
// 這里就像你自己的耳朵一直在等待接收通知
while (true) {
    pcntl_signal_dispatch();
}

執行 php index.php 命令啟動程序,然后通過 kill 向進程發送 SIGTERM 信號,最終會在 signalHandler 回調函數中捕獲到該信號。

[manongsen@root php_signal]$ php index.php 
當前進程 PID: 43195

[manongsen@root php_signal]$ kill -SIGTERM 43195

[manongsen@root php_signal]$ php index.php 
當前進程 PID: 43195
捕獲到 SIGTERM 信號...

再來看一個進程間信號傳遞的例子,假設你在當前這家公司被內耗死了,然后你開始找一個替補的人,好不容易找到一個卷王,但是干了一段時間就跑路了,最后你也無奈了索性也擺爛跑路了。

使用 pcntl_fork 創建了一個子進程,父進程中調用 pcntl_signal 函數給 SIGCHLD 信號注冊回調函數 signalHandler 然后父進程一直監聽信號。直到子進程 exit 退出了,最后父進程觸發了回調函數 signalHandler 開始回收子進程資源,之后父進程也 exit 退出了。

<?php

// 定義信號處理函數
function signalHandler($signal)
{
    $pid = getmypid();
    // 回收子進程資源
    // 你接收到了他跑路的信號,開始回收他的代碼權限、賬號等資源
    $status = 0;
    $cid = pcntl_wait($status, \WUNTRACED);
    echo "父進程: {$pid}, 收到子進程: {$cid}, 退出信號: {$signal}" . PHP_EOL;
    
    // 最后你抵不住也跑路了
    echo "父進程: {$pid}, 最后也退出了" . PHP_EOL;
    exit;
}

// 在當前進程 Fork 一個子進程
// 比如終于招聘了一個替補你的人
$pid = pcntl_fork();
if ( $pid < 0 ) {
 // 這里被你放鴿子了
 // 你也擺爛直接跑路算了
    exit('fork error');
} else if( $pid > 0 ) {
   // 父進程執行空間 ...
   echo "父進程: " . getmypid() . PHP_EOL;

   // 注冊信號回調函數
   // SIGCHLD 表示一個子進程已經終止或停止
   // 你相當于給他觸發跑路的信號,準備了一個處理方案
   pcntl_signal(SIGCHLD, "signalHandler");

   // 監聽信號
   // 你相當于一直監聽他的動向
   while(true){
      pcntl_signal_dispatch();
   }
} 

// 子進程執行空間 ...
$cid = getmypid();
echo "子進程: {$cid}" . PHP_EOL;

// 休眠 5 秒鐘
sleep(5);

// 退出
// 假設他干了5秒鐘就跑路了
exit;

執行 php index.php 可以看到相應的執行結果。

[manongsen@root php_signal]$ php index.php 
父進程: 47008
子進程: 47009
子進程: 47009 退出了
父進程: 47008, 收到子進程: 47009, 退出信號: 20
父進程: 47008, 最后也退出了

有了上面這兩個例子的基礎,我們再來看 Workerman 源碼中對信號的使用就會更得心應手,翻開 Worker.php 文件中的第 548 行,這個 runAll 函數中會調用 installSignal 函數注冊信號回調函數,還會調用 monitorWorkers 函數時刻監聽子進程的信號。

<?php

// workerman/Worker.php:548
public static function runAll()
{
    // ...

    // 注冊信號回調函數
    static::installSignal();
    
    // ...
    
    // 監聽 Worker 子進程信號
    static::monitorWorkers();
}

看到 installSignal 這個函數的實現,是不是有種似曾相似的感覺,和上面例子的注冊方式基本一樣。

<?php

// workerman/Worker.php:1140
protected static function installSignal()
{
    // ...
    // 信號回調函數
    $signalHandler = '\Workerman\Worker::signalHandler';
    // 退出信號
    \pcntl_signal(\SIGINT, $signalHandler, false);
    \pcntl_signal(\SIGTERM, $signalHandler, false);
    \pcntl_signal(\SIGHUP, $signalHandler, false);
    \pcntl_signal(\SIGTSTP, $signalHandler, false);
    // 優雅退出信號
    \pcntl_signal(\SIGQUIT, $signalHandler, false);
    // 重載信號
    \pcntl_signal(\SIGUSR1, $signalHandler, false);
    // 優雅重載信號
    \pcntl_signal(\SIGUSR2, $signalHandler, false);
    // 狀態信號
    \pcntl_signal(\SIGIOT, $signalHandler, false);
    // 連接狀態信號
    \pcntl_signal(\SIGIO, $signalHandler, false);
    // SIGPIPE 管道信號,忽略該信號
    \pcntl_signal(\SIGPIPE, \SIG_IGN, false);
}

這個函數 signalHandler 接收到子進程的信號,并且根據不同的信號 switch 到不同的 case 分支。

<?php

// workerman/Worker.php:1220
public static function signalHandler($signal)
{
    switch ($signal) {
        // 退出
        case \SIGINT:
        case \SIGTERM:
        case \SIGHUP:
        case \SIGTSTP:
            static::$_gracefulStop = false;
            // 這里會把所有的 Woker 子進程 Kill 掉,并且銷毀相應子進程的 Event-Loop 事件循環
            static::stopAll();
            break;
        // 優雅的退出
        case \SIGQUIT:
            // 優雅退出在調用 posix_kill 函數時傳遞的是 SIGTERM 信號
            static::$_gracefulStop = true;
            static::stopAll();
            break;
        // 重載
        case \SIGUSR2:
        case \SIGUSR1:
            if (static::$_status === static::STATUS_SHUTDOWN || static::$_status === static::STATUS_RELOADING) {
                return;
            }
            static::$_gracefulStop = $signal === \SIGUSR2;
            static::$_pidsToRestart = static::getAllWorkerPids();
            // 對 Worker 進程實現重載
            static::reload();
            break;
        // 展現狀態
        case \SIGIOT:
            // 這里會輸出一些 全局狀態信息、進程狀態信息
            // 比如 load-average、event-loop、workers-count 等
            static::writeStatisticsToStatusFile();
            break;
        // 展現連接狀態
        case \SIGIO:
            // 這里會輸出一些進程連接相關的統計信息
            // 比如 Recv-Q、Send-Q、Bytes-R、Bytes-W、Status 等
            static::writeConnectionsStatisticsToStatusFile();
            break;
    }
}

最后啟動監聽函數 monitorWorkersForLinux 除了監聽信號之后,還做了些資源的回收工作,此外還會回調用戶自定義的 Stop 函數,便于做一些業務上的處理。

<?php

// workerman/Worker.php:1735
protected static function monitorWorkersForLinux()
{
    // 默認運行狀態
    static::$_status = static::STATUS_RUNNING;
    // 一直監聽直到所有的進程退出
    while (1) {
        \pcntl_signal_dispatch();
        $status = 0;
        // 這些做一些子進程資源的回收工作
        $pid    = \pcntl_wait($status, \WUNTRACED);
        \pcntl_signal_dispatch();

        // ...

        // 做一些清理工作
        if (static::$_status === static::STATUS_SHUTDOWN && !static::getAllWorkerPids()) {
            // 這里還會回調用戶設定的 Stop 回調函數,便于做一些業務上的處理
            // 然后直接 exit 當前進程
            static::exitAndClearAll();
        }
    }
}

在涉及到多進程編程的場景中信號的使用最為頻繁,比如 Workerman 在 Master 主進程中通過信號監聽了 Worker 子進程的狀態,可以及時獲取到子進程的運行信息,一旦子進程掛了也可以迅速的拉取,在整個服務進程都退出后也可以及時回收內存資源。

還有在 Linux 系統下我們也經常使用 Kill 命令來向進程傳遞信號,不過大多數情況都是用在了強制結束進程上,這是最簡單粗暴殺死進程的方法。不過我之前遇到過某些病毒進程,無論如何都 Kill 不掉,不知大家有沒有見識過?

這次我主要分享了信號的一些基礎知識,以及在 Workerman 源碼中的使用姿勢,大家最好是可以實踐一下文中的這些例子,實踐過后相信對信號會有更深一步的理解,希望對大家能有所幫助。

責任編輯:武曉燕 來源: 碼農先森
相關推薦

2025-01-26 00:00:15

PHP協程控制權

2024-08-12 08:43:09

2012-08-08 10:10:31

PHP

2015-03-27 11:42:44

日志管理PHPSeasLog

2024-02-01 09:21:08

RevoltPHP高性能

2022-08-15 08:01:35

微服務框架RPC

2024-10-18 10:27:50

PHP框架webma

2024-11-04 08:16:08

Go語言Web 框架

2023-10-31 18:52:29

網絡框架XDP技術

2020-06-17 16:43:40

網絡IO框架

2024-04-28 10:17:30

gnetGo語言

2024-02-26 07:43:10

大語言模型LLM推理框架

2018-02-28 10:11:50

騰訊框架開源

2024-12-24 10:50:05

GinWeb開發

2019-07-31 14:36:46

Linux服務器框架

2025-01-13 12:23:51

2011-06-20 15:40:19

QT 信號

2024-11-06 11:25:06

2017-03-16 11:39:33

Openstack源碼姿勢

2025-01-13 13:00:00

Go網絡框架nbio
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚洲欧美日韩精品 | 手机看片1 | 精品欧美激情在线观看 | 午夜免费影视 | 日本一区精品 | 国产在线看片 | 草草视频在线观看 | 免费一区 | 国产精品视频播放 | 精品少妇一区二区三区日产乱码 | 国产免费又黄又爽又刺激蜜月al | 美女啪啪国产 | 欧美一区二区 | 欧美高清视频在线观看 | av在线播放免费 | 国际精品鲁一鲁一区二区小说 | 成人免费观看男女羞羞视频 | 国产一区二区三区四区区 | 亚洲欧美国产毛片在线 | 免费国产成人av | 成人国产网站 | 亚洲精品一区二区三区蜜桃久 | 亚洲成人av | 精品婷婷| 狠狠骚| 国产精品视频一二三区 | 久久久久久久一区二区三区 | 国产99久久久国产精品下药 | 国产日韩欧美一区 | 国产一级片精品 | 亚洲一区二区三区四区五区午夜 | 久久一区二区三区四区 | 一区二区三区视频播放 | 日韩在线不卡 | 一区二区三区中文 | 在线激情视频 | 国内自拍视频在线观看 | 亚洲国产网 | 国产成人在线视频 | 国产精品1区2区 | 精品日韩一区 |