偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

看看 PHP 高性能框架 Workerman 源碼中信號(hào)的使用姿勢(shì)

開(kāi)發(fā) 前端
信號(hào)是一種輕量級(jí)的消息傳遞機(jī)制,通常用于軟件中斷異步通知。比如我們常用的 kill -9 xxx? 就是對(duì) xxx 程序發(fā)送了強(qiáng)制終止其進(jìn)程的 SIGKILL 信號(hào),還有我們經(jīng)常會(huì)用的 Ctrl+C 或 Ctrl+D 等鍵盤(pán)操作事件,也會(huì)產(chǎn)生 SIGINT 終端退出信號(hào)。

大家好,我是碼農(nóng)先森。

信號(hào)是一種輕量級(jí)的消息傳遞機(jī)制,通常用于軟件中斷異步通知。比如我們常用的 kill -9 xxx 就是對(duì) xxx 程序發(fā)送了強(qiáng)制終止其進(jìn)程的 SIGKILL 信號(hào),還有我們經(jīng)常會(huì)用的 Ctrl+C 或 Ctrl+D 等鍵盤(pán)操作事件,也會(huì)產(chǎn)生 SIGINT 終端退出信號(hào)。

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

不過(guò)如果經(jīng)常使用 Workerman 或 Swoole 編程的朋友,那對(duì)信號(hào)應(yīng)該頗有了解了吧。這里不管大家有沒(méi)有接觸過(guò)信號(hào),都建議學(xué)習(xí)其原理和用法,興許對(duì)以后的編程有所幫助,畢竟技術(shù)這東西就怕用時(shí)方恨少。為了更通俗易懂的描述信號(hào),我用一個(gè)大白話的例子來(lái)講解一下。假設(shè)你是一個(gè)打工仔,這里好像不用假設(shè)因?yàn)槟憔褪莻€(gè)打工仔哈哈,這時(shí)你正戴著耳機(jī)沉浸在代碼的世界了。

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

這里的公司你可以理解為操作系統(tǒng),廣播通知理解為信號(hào),你自己理解為一個(gè)進(jìn)程。你在接收到信號(hào)之后,可以做對(duì)應(yīng)的事情,同樣也可以做與通知不相干的事情,比如通知叫你改線上的 Bug 但是你舍不得下午茶還想繼續(xù)吃,那你也可以任性不及時(shí)改「只不過(guò)事后就要挨批了哈哈」。

還有就是也可以把通知不當(dāng)回事,不予理會(huì)完全忽略不處理。這段大白話雖說(shuō)的好,但中國(guó)有句古話「說(shuō)的好聽(tīng)不如做的好看」,那么接下來(lái)我們就以實(shí)際的例子來(lái)以身入代碼。

下面這個(gè)是當(dāng)前進(jìn)程接收外部信號(hào)的例子,使用 pcntl_signal 注冊(cè)指定信號(hào)的回調(diào)函數(shù),回調(diào)函數(shù) signalHandler 會(huì)根據(jù)接收到的不同信號(hào)進(jìn)入到相應(yīng)的 switch 分支中執(zhí)行對(duì)應(yīng)的處理邏輯。

處理完之后,便可以退出程序。其中的 pcntl_signal_dispatch 函數(shù)起著監(jiān)聽(tīng)待處理信號(hào),并執(zhí)行信號(hào)所注冊(cè)回調(diào)函數(shù)的作用。還有 where(true) 會(huì)一直循環(huán)執(zhí)行,直到接收到信號(hào)。

<?php

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

// 注冊(cè)信號(hào)回調(diào)函數(shù)
// 就好比接受到不同的通知,就應(yīng)該要做對(duì)應(yīng)的事情
// 這里統(tǒng)一回調(diào) signalHandler 這個(gè)函數(shù),你也可以定義不同的函數(shù)
pcntl_signal(SIGINT, 'signalHandler');
pcntl_signal(SIGTERM, 'signalHandler');

// 輸出一下當(dāng)前的進(jìn)程號(hào),方便測(cè)試
echo "當(dāng)前進(jìn)程 PID: " . getmypid() . PHP_EOL;

// 不斷監(jiān)聽(tīng)信號(hào),接收到信號(hào)后調(diào)用相應(yīng)的信號(hào)回調(diào)函數(shù)
// 這里就像你自己的耳朵一直在等待接收通知
while (true) {
    pcntl_signal_dispatch();
}

執(zhí)行 php index.php 命令啟動(dòng)程序,然后通過(guò) kill 向進(jìn)程發(fā)送 SIGTERM 信號(hào),最終會(huì)在 signalHandler 回調(diào)函數(shù)中捕獲到該信號(hào)。

[manongsen@root php_signal]$ php index.php 
當(dāng)前進(jìn)程 PID: 43195

[manongsen@root php_signal]$ kill -SIGTERM 43195

[manongsen@root php_signal]$ php index.php 
當(dāng)前進(jìn)程 PID: 43195
捕獲到 SIGTERM 信號(hào)...

再來(lái)看一個(gè)進(jìn)程間信號(hào)傳遞的例子,假設(shè)你在當(dāng)前這家公司被內(nèi)耗死了,然后你開(kāi)始找一個(gè)替補(bǔ)的人,好不容易找到一個(gè)卷王,但是干了一段時(shí)間就跑路了,最后你也無(wú)奈了索性也擺爛跑路了。

使用 pcntl_fork 創(chuàng)建了一個(gè)子進(jìn)程,父進(jìn)程中調(diào)用 pcntl_signal 函數(shù)給 SIGCHLD 信號(hào)注冊(cè)回調(diào)函數(shù) signalHandler 然后父進(jìn)程一直監(jiān)聽(tīng)信號(hào)。直到子進(jìn)程 exit 退出了,最后父進(jìn)程觸發(fā)了回調(diào)函數(shù) signalHandler 開(kāi)始回收子進(jìn)程資源,之后父進(jìn)程也 exit 退出了。

<?php

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

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

   // 注冊(cè)信號(hào)回調(diào)函數(shù)
   // SIGCHLD 表示一個(gè)子進(jìn)程已經(jīng)終止或停止
   // 你相當(dāng)于給他觸發(fā)跑路的信號(hào),準(zhǔn)備了一個(gè)處理方案
   pcntl_signal(SIGCHLD, "signalHandler");

   // 監(jiān)聽(tīng)信號(hào)
   // 你相當(dāng)于一直監(jiān)聽(tīng)他的動(dòng)向
   while(true){
      pcntl_signal_dispatch();
   }
} 

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

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

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

執(zhí)行 php index.php 可以看到相應(yīng)的執(zhí)行結(jié)果。

[manongsen@root php_signal]$ php index.php 
父進(jìn)程: 47008
子進(jìn)程: 47009
子進(jìn)程: 47009 退出了
父進(jìn)程: 47008, 收到子進(jìn)程: 47009, 退出信號(hào): 20
父進(jìn)程: 47008, 最后也退出了

有了上面這兩個(gè)例子的基礎(chǔ),我們?cè)賮?lái)看 Workerman 源碼中對(duì)信號(hào)的使用就會(huì)更得心應(yīng)手,翻開(kāi) Worker.php 文件中的第 548 行,這個(gè) runAll 函數(shù)中會(huì)調(diào)用 installSignal 函數(shù)注冊(cè)信號(hào)回調(diào)函數(shù),還會(huì)調(diào)用 monitorWorkers 函數(shù)時(shí)刻監(jiān)聽(tīng)子進(jìn)程的信號(hào)。

<?php

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

    // 注冊(cè)信號(hào)回調(diào)函數(shù)
    static::installSignal();
    
    // ...
    
    // 監(jiān)聽(tīng) Worker 子進(jìn)程信號(hào)
    static::monitorWorkers();
}

看到 installSignal 這個(gè)函數(shù)的實(shí)現(xiàn),是不是有種似曾相似的感覺(jué),和上面例子的注冊(cè)方式基本一樣。

<?php

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

這個(gè)函數(shù) signalHandler 接收到子進(jìn)程的信號(hào),并且根據(jù)不同的信號(hào) 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;
            // 這里會(huì)把所有的 Woker 子進(jìn)程 Kill 掉,并且銷毀相應(yīng)子進(jìn)程的 Event-Loop 事件循環(huán)
            static::stopAll();
            break;
        // 優(yōu)雅的退出
        case \SIGQUIT:
            // 優(yōu)雅退出在調(diào)用 posix_kill 函數(shù)時(shí)傳遞的是 SIGTERM 信號(hào)
            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();
            // 對(duì) Worker 進(jìn)程實(shí)現(xiàn)重載
            static::reload();
            break;
        // 展現(xiàn)狀態(tài)
        case \SIGIOT:
            // 這里會(huì)輸出一些 全局狀態(tài)信息、進(jìn)程狀態(tài)信息
            // 比如 load-average、event-loop、workers-count 等
            static::writeStatisticsToStatusFile();
            break;
        // 展現(xiàn)連接狀態(tài)
        case \SIGIO:
            // 這里會(huì)輸出一些進(jìn)程連接相關(guān)的統(tǒng)計(jì)信息
            // 比如 Recv-Q、Send-Q、Bytes-R、Bytes-W、Status 等
            static::writeConnectionsStatisticsToStatusFile();
            break;
    }
}

最后啟動(dòng)監(jiān)聽(tīng)函數(shù) monitorWorkersForLinux 除了監(jiān)聽(tīng)信號(hào)之后,還做了些資源的回收工作,此外還會(huì)回調(diào)用戶自定義的 Stop 函數(shù),便于做一些業(yè)務(wù)上的處理。

<?php

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

        // ...

        // 做一些清理工作
        if (static::$_status === static::STATUS_SHUTDOWN && !static::getAllWorkerPids()) {
            // 這里還會(huì)回調(diào)用戶設(shè)定的 Stop 回調(diào)函數(shù),便于做一些業(yè)務(wù)上的處理
            // 然后直接 exit 當(dāng)前進(jìn)程
            static::exitAndClearAll();
        }
    }
}

在涉及到多進(jìn)程編程的場(chǎng)景中信號(hào)的使用最為頻繁,比如 Workerman 在 Master 主進(jìn)程中通過(guò)信號(hào)監(jiān)聽(tīng)了 Worker 子進(jìn)程的狀態(tài),可以及時(shí)獲取到子進(jìn)程的運(yùn)行信息,一旦子進(jìn)程掛了也可以迅速的拉取,在整個(gè)服務(wù)進(jìn)程都退出后也可以及時(shí)回收內(nèi)存資源。

還有在 Linux 系統(tǒng)下我們也經(jīng)常使用 Kill 命令來(lái)向進(jìn)程傳遞信號(hào),不過(guò)大多數(shù)情況都是用在了強(qiáng)制結(jié)束進(jìn)程上,這是最簡(jiǎn)單粗暴殺死進(jìn)程的方法。不過(guò)我之前遇到過(guò)某些病毒進(jìn)程,無(wú)論如何都 Kill 不掉,不知大家有沒(méi)有見(jiàn)識(shí)過(guò)?

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

責(zé)任編輯:武曉燕 來(lái)源: 碼農(nóng)先森
相關(guān)推薦

2025-01-26 00:00:15

PHP協(xié)程控制權(quán)

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

微服務(wù)框架RPC

2024-10-18 10:27:50

PHP框架webma

2024-11-04 08:16:08

Go語(yǔ)言Web 框架

2023-10-31 18:52:29

網(wǎng)絡(luò)框架XDP技術(shù)

2020-06-17 16:43:40

網(wǎng)絡(luò)IO框架

2024-04-28 10:17:30

gnetGo語(yǔ)言

2024-02-26 07:43:10

大語(yǔ)言模型LLM推理框架

2018-02-28 10:11:50

騰訊框架開(kāi)源

2024-12-24 10:50:05

GinWeb開(kāi)發(fā)

2019-07-31 14:36:46

Linux服務(wù)器框架

2025-01-13 12:23:51

2011-06-20 15:40:19

QT 信號(hào)

2024-11-06 11:25:06

2017-03-16 11:39:33

Openstack源碼姿勢(shì)

2025-01-13 13:00:00

Go網(wǎng)絡(luò)框架nbio
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)