国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

PHP回顧之多進程編程

lifesimple / 3128人閱讀

摘要:多進程中與多進程相關(guān)的兩個重要拓展是和。函數(shù)執(zhí)行期間,主進程除了等待無法處理其他任務(wù),所以一般不認為這是多進程編程。回收子進程有兩種方式,一種是主進程調(diào)用函數(shù)等待子進程結(jié)束另外一種是處理信號。

轉(zhuǎn)載請注明文章出處: https://tlanyan.me/php-review...
PHP回顧系列目錄

PHP基礎(chǔ)

web請求

cookie

web響應(yīng)

session

數(shù)據(jù)庫操作

加解密

Composer

創(chuàng)建自己的Composer包

發(fā)送郵件

IO

Socket編程

為了更好的利用多核CPU,我們需要多進程或多線程。但在常規(guī)web開發(fā)中,我們極少用到這兩種并發(fā)技術(shù)(curl_multi等特殊函數(shù)除外)。如果腳本運行在CLI模式下,多進程和多線程技術(shù)是提高多核CPU的有力工具。

相對于多線程,多進程的程序具有健壯、無鎖、對分布式支持更好等特點。本文來學(xué)習(xí)一下PHP的多進程編程。

多進程

PHP中與(多)進程相關(guān)的兩個重要拓展是PCNTLPOSIXPCNTL主要用來創(chuàng)建、執(zhí)行子進程和處理信號,POSIX拓展則實現(xiàn)了POSIX標準中定義的接口。由于Windows不是POSIX兼容的,所以POSIX拓展在Windows平臺上不可用。

先上簡單的代碼看多進程編程:

// fork.php
$parentId = posix_getpid();
fwrite(STDOUT, "my pid: $parentId
");
$childNum = 10;
foreach (range(1, $childNum) as $index) {
    $pid = pcntl_fork();
    if ($pid === -1) {
        fwrite(STDERR, "failt to fork!
");
        exit;
    }
    // parent code
    if ($pid > 0) {
        fwrite(STDOUT, "fork the {$index}th child, pid: $pid
");
    } else {
        $mypid = posix_getpid();
        $parentId = posix_getppid();
        fwrite(STDOUT, "I"m the {$index}th child and my pid: $mypid, parentId: $parentId
");
        sleep(5);
        exit;               // 注意這一行
    }
}

關(guān)鍵的代碼是pcntl_fork函數(shù),函數(shù)返回一個整數(shù),小于0表示克隆失敗。克隆成功的情況下返回兩個值:父進程拿到子進程的進程號,而子進程則得到0。可以根據(jù)函數(shù)的返回值判斷接下來的執(zhí)行環(huán)境在父進程中還是子進程中。

fork調(diào)用讓系統(tǒng)創(chuàng)建一個與當(dāng)前進程幾乎完全一樣的進程,除了進程號等少數(shù)信息不一樣,進程的代碼段、堆棧、數(shù)據(jù)段的值都一致。父進程打開了一個文件,復(fù)制的子進程同樣享有這個句柄,這是過去多進程能監(jiān)聽同一個端口的原理;子進程基于父進程fork時的環(huán)境繼續(xù)執(zhí)行(代碼段共享)直到退出。

去掉上述代碼中else語句塊的exit能將幫助你更好地理解上面這段話。程序的本意是生成10個子進程,去掉子進程執(zhí)行代碼的exit后,子進程執(zhí)行完else塊中代碼后繼續(xù)執(zhí)行foreach循環(huán),最終生成55個子進程(為什么是55個?)!鑒于此,一個良好的實踐是在子進程的執(zhí)行代碼后總是加上exit終止語句,除非你真的有把握子進程會按照預(yù)期執(zhí)行。

除了fork,另外一種多進程技術(shù)是exec。systemexecproc_open等函數(shù)會生成一個新的進程執(zhí)行外部命令(并返回結(jié)果)。這些函數(shù)的本質(zhì)是fork一個進程,然后調(diào)用shell執(zhí)行命令,主進程等待其執(zhí)行結(jié)束。函數(shù)執(zhí)行期間,主進程除了等待無法處理其他任務(wù),所以一般不認為這是多進程編程。實踐中可以結(jié)合fork來并發(fā)執(zhí)行外部命令。

孤兒進程與僵尸進程

多進程編程需要考慮到的一個問題是孤兒進程和僵尸進程。進程結(jié)束前父進程已經(jīng)退出,進程變成孤兒進程;進程退出后父進程在執(zhí)行且未回收子進程,那么進程變成僵尸進程。孤兒進程是仍在執(zhí)行的進程,僵尸進程則已經(jīng)停止執(zhí)行,只剩下進程號一縷孤魂仍能被外界感知。

孤兒進程會被系統(tǒng)的根進程(init進程,進程號為1)接管,運行結(jié)束后由根進程回收。下面代碼演示孤兒進程的父進程的變化:

// orphan.php
$pid = pcntl_fork();
if ($pid === 0) {
    $myid = posix_getpid();
    $parentId = posix_getppid();
    fwrite(STDOUT, "my pid: $myid, parentId: $parentId
");
    sleep(5);
    $myid = posix_getpid();
    $parentId = posix_getppid();
    fwrite(STDOUT, "my pid: $myid, parentId: $parentId
");
} else {
    fwrite(STDOUT, "parent exit
");
}

執(zhí)行腳本:php orphan.php,可以看到類似如下輸出:

parent exit
my pid: 14384, parentId: 14383
my pid: 14384, parentId: 1

父進程退出后子進程過繼給1號根進程,并由其負責(zé)回收子進程。

接著看僵尸進程。主進程長時間運行且不回收子進程,僵尸進程會一直存在,直到主進程退出后變成孤兒進程過繼給根進程;如果主進程一直運行,僵尸進程將一直存在。

下面代碼演示生成10個僵尸進程:

// zombie.php
foreach (range(1, 10) as $i) {
    $pid = pcntl_fork();
    if ($pid === 0) {
        fwrite(STDOUT, "child exit
");
        exit;
    }
}
sleep(200);
exit;

打開終端執(zhí)行php zombie.php,然后新打開一個終端執(zhí)行ps aux | grep php | grep -v grep,一個可能的輸出如下:

vagrant  14336  0.3  0.8 344600 15144 pts/1    S+   05:09   0:00 php zombie.php
vagrant  14337  0.0  0.0      0     0 pts/1    Z+   05:09   0:00 [php] 
vagrant  14338  0.0  0.0      0     0 pts/1    Z+   05:09   0:00 [php] 
vagrant  14339  0.0  0.0      0     0 pts/1    Z+   05:09   0:00 [php] 
vagrant  14340  0.0  0.0      0     0 pts/1    Z+   05:09   0:00 [php] 
vagrant  14341  0.0  0.0      0     0 pts/1    Z+   05:09   0:00 [php] 
vagrant  14342  0.0  0.0      0     0 pts/1    Z+   05:09   0:00 [php] 
vagrant  14343  0.0  0.0      0     0 pts/1    Z+   05:09   0:00 [php] 
vagrant  14344  0.0  0.0      0     0 pts/1    Z+   05:09   0:00 [php] 
vagrant  14345  0.0  0.0      0     0 pts/1    Z+   05:09   0:00 [php] 
vagrant  14346  0.0  0.0      0     0 pts/1    Z+   05:09   0:00 [php] 

最后一列為的進程便是僵尸進程,這些進程的第八列的標志是“Z+”,即Zombie。雖然除了進程號無法回收,僵尸進程并不像僵尸那么恐怖,但我們應(yīng)該在子進程執(zhí)行結(jié)束后讓其安息,避免出現(xiàn)僵尸進程。

回收子進程有兩種方式,一種是主進程調(diào)用pcntl_wait/pcntl_waitpid函數(shù)等待子進程結(jié)束;另外一種是處理SIGCLD信號。我們先說使用wait函數(shù)回收子進程,信號處理放在下面的章節(jié)。

PCNT拓展中用于回收子進程的兩個函數(shù)是pcntl_waitpcntl_waitpidpcntl_waitpid可以指定等待的進程。來看如何用這兩個函數(shù)回收子進程:

// wait.php
$pid = pcntl_fork();
if ($pid === 0) {
    $myid = posix_getpid();
    fwrite(STDOUT, "child $myid exited
");
} else {
    sleep(5);
    $status = 0;
    $pid = pcntl_wait($status, WUNTRACED);
    if ($pid > 0) {
        fwrite(STDOUT, "child: $pid exited
");
    }

    sleep(5);
    fwrite(STDOUT, "parent exit
");
}

執(zhí)行腳本:php wait.php,然后打開另外一個終端執(zhí)行:watch -n2 "ps aux | grep php | grep -v grep"。從watch輸出可以看到子進程退出后的5秒內(nèi)是僵尸進程,父進程回收后僵尸進程消失,最后父進程退出。

如果有多個子進程,父進程需要循環(huán)調(diào)用wait函數(shù),否則某些子進程執(zhí)行完畢后也會變成僵尸進程。

信號處理

PCNTL拓展中的pcntl_signal函數(shù)用于安裝信號函數(shù),進程收到信號時會執(zhí)行回調(diào)函數(shù)中的代碼。我們知道Ctrl + C可以中斷程序的執(zhí)行,原理是按下組合鍵后系統(tǒng)向程序發(fā)出SIGINT信號。這個信號的默認操作是退出程序,所以系統(tǒng)終止了程序運行。SIGINT信號可捕捉信號,我們可以設(shè)置信號回調(diào)函數(shù),收到信號后系統(tǒng)執(zhí)行回調(diào)函數(shù)而非退出程序:

// signal.php
pcntl_signal(SIGINT, function () {
    fwrite(STDOUT, "receive signal: SIGINT, do nothing...
");
});

while (true) {
    pcntl_signal_dispatch();
    sleep(1);
}

執(zhí)行腳本:php signal.php,然后按Ctrl + C,輸出如下:

[vagrant@localhost ~]$ php signal.php
^Creceive signal: SIGINT, do nothing...

^Creceive signal: SIGINT, do nothing...
^Creceive signal: SIGINT, do nothing...
^Creceive signal: SIGINT, do nothing...

^Creceive signal: SIGINT, do nothing...

安裝了信號函數(shù)后,Ctrl + C不再好使,程序依舊調(diào)皮的執(zhí)行。要結(jié)束程序,可以向進程發(fā)送無法捕捉的信號,例如SIGKILLps aux | grep php找到程序的進程號,然后用kill命令發(fā)送SIGKILL信號:kill -SIGKILL 進程號。程序收到信號后被操作系統(tǒng)強制中斷執(zhí)行。

如果在代碼中捕捉SIGKILL信號會怎么樣?將上面代碼中的SIGINT改成SIGKILL,執(zhí)行腳本會提示:PHP Fatal error: Error installing signal handler for 9 in /home/vagrant/signal.php on line 2。9是SIGKILL的值,錯誤表示代碼中不能捕捉這個信號。

支持哪些信號,默認操作是什么,和系統(tǒng)相關(guān)。絕大部分*nix系統(tǒng)支持SIGINTSIGKILL等31個常見異步信號,某些系統(tǒng)支持更多的信號。

內(nèi)核收到進程信號后,會查看進程是否注冊了處理函數(shù),如果未注冊則執(zhí)行默認操作;否則當(dāng)進程運行在用戶態(tài)時,內(nèi)核回調(diào)信號處理函數(shù)并移除信號。PHP中收到信號后觸發(fā)信號回調(diào)函數(shù)的方式有三種:

tick觸發(fā),例如每執(zhí)行100條低級指令檢查信號:declare(ticks=100)

使用pcntl_signal_dispatch手動觸發(fā),用法見上文signal.php

PHP7.1起可以使用pcntl_async_signals異步智能觸發(fā)。

tick的方式十分低效,不建議使用;pcntl_signal_dispatch需要手動觸發(fā),可能存在較大延遲。如果PHP的版本不低于7.1,建議使用pcnt_async_signals自動分發(fā)信號消息。這個函數(shù)效率上比tick高,實時性上比手動觸發(fā)強。其原理是當(dāng)程序從內(nèi)核態(tài)切出、函數(shù)返回等時機檢查是否有信號,有則執(zhí)行回調(diào)。

理解了信號,再看看如何使用信號解決僵尸進程問題。子進程退出后,操作系統(tǒng)會發(fā)送SIGCLD信號到父進程,在信號回調(diào)函數(shù)中回收子進程即可,詳情見下面代碼:

// fork-signal.php
pcntl_async_signals(true);

pcntl_signal(SIGCLD, function () {
    $pid = pcntl_wait($status, WUNTRACED);
    fwrite(STDOUT, "child: $pid exited
");
});

$pid = pcntl_fork();
if ($pid === 0) {
    fwrite(STDOUT, "child exit
");
} else {
    // mock busy work
    sleep(1);
}

相對于手動pcntl_wait/pcntl_waitpid方式,信號處理無疑更為簡潔高效。

信號也是進程中通信的一種方式。接下來簡要說一下進程間通信。

進程間通信

fork出子進程后,兩個進程的數(shù)據(jù)段和堆棧(理論上)均分開。與多線程不同,全局變量在不同進程中無法共享。進程間要進行數(shù)據(jù)交換,必須通過進程間通信(Inter-Process Communication)技術(shù)。上文提到的信號是進程中通信技術(shù)的一種,posix_kill函數(shù)可以向指定進程發(fā)送信號,達到通信的目的。

進程間通信技術(shù)主要有:

管道(pipe),流管道(s_pipe)和有名管道(FIFO);

信號(signal);

消息隊列(message queue);

共享內(nèi)存(share memory);

信號量(semaphore);

套接字(socket);

這些通信技術(shù)的詳細內(nèi)容請參考文末的鏈接,或者其他文獻,本文不再詳述。

守護進程

通過php test.php方式執(zhí)行程序,關(guān)閉終端后程序會退出。要讓程序能長期執(zhí)行,需要額外的手段。總結(jié)起來主要有三種:

nohup

screen/tmux等工具;

fork子進程后,父進程退出,子進程升為會話/進程組長,脫離終端繼續(xù)運行。

screen/tmux方式程序?qū)嶋H上仍停留在終端,只是運行在一個長期存在的終端中。nohup和fork方式才是讓程序脫離(detach)終端,達到肉體飛升的正道(成為daemon)。

下面的代碼通過fork的方式讓程序成為守護進程:

// daemon.php
$pid = pcntl_fork();
switch ($pid) {
case -1:
    fwrite(STDOUT, "fork failed!
");
    exit(1);
    break;

case 0:
    if (posix_setsid() === -1) {
        fwrite(STDERR, "fail to set child as the session leader!
");
        exit;
    }
    file_put_contents("/tmp/daemon.out", "php daemon example
", FILE_APPEND);
    while (true) {
        sleep(5);
        file_put_contents("/tmp/daemon.out", "now: " . date("Y-m-d H:i:s") . "
", FILE_APPEND);
    }
    break;

default:
    // parent exit
    exit;
}

fork之后最重要的一個操作是posix_setsid,該函數(shù)把當(dāng)前進程設(shè)置為會話組長(被設(shè)置的進程當(dāng)前不能是組長)。某些開源庫中會fork兩次,防止第一次fork的進程無意間打開終端(非會話組長無法打開終端)。

執(zhí)行程序:php daemon.php,然后關(guān)閉終端,或者重新登錄,通過ps aux | grep daemon.php查看程序均在執(zhí)行。檢測/tmp/daemon.out,不斷有內(nèi)容輸出,說明程序已經(jīng)成為在后臺持續(xù)運行的守護進程。

注意后臺的多進程應(yīng)當(dāng)在進程脫離終端后再fork,即最終在后臺干活的進程不能直接從腳本啟動的進程fork,而應(yīng)該至少是腳本啟動進程的孫子進程。

應(yīng)用

下面來說一個多進程的簡單應(yīng)用。在上一篇博文“PHP回顧之Socket編程”,我們的服務(wù)端已經(jīng)能做到幾乎實時響應(yīng)客戶端的請求,但是客戶端不是實時收到服務(wù)端下發(fā)的消息。利用多進程,我們用一個進程專門負責(zé)讀取服務(wù)端的消息,另一個進程則負責(zé)收集用戶在終端的輸入,然后發(fā)送到服務(wù)端。下面是多進程的客戶端代碼:

// client.php
 "echo",
                "args" => $args,
            ]);

            fwrite($socket, $message);
        }
    }
}

執(zhí)行客戶端:php client.php,會發(fā)現(xiàn)終端輸入和服務(wù)端消息都能及時響應(yīng)。同時,連接斷開的信號也被正確的廣播。

總結(jié)

本文簡要介紹了多進程編程的幾個方面,最后給出一個應(yīng)用的例子,希望對學(xué)習(xí)多進程的同行有幫助。

感謝閱讀!

參考

http://php.net/manual/en/book...

http://php.net/manual/en/book...

https://www.cnblogs.com/hicji...

http://gityuan.com/2015/12/20...

https://www.cnblogs.com/hoys/...

http://www.cnblogs.com/taobat...

https://www.jianshu.com/p/c10...

https://blog.csdn.net/column/...

https://segmentfault.com/a/11...

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/28899.html

相關(guān)文章

  • PHPSocket編程之多進程的回聲服務(wù)器

    摘要:所以這次采用多進程的方式來實現(xiàn)同時為多個客戶端提供服務(wù)。而多進程則是通過創(chuàng)建多個進程來共同完成一件事。如果是子進程的執(zhí)行環(huán)境,則返回。正常情況下,子進程是通過父進程創(chuàng)建的。以上則是我們的多進程回聲服務(wù)程序。 上次的回聲服務(wù)程序有個很大的缺點,就是只能同時連接一個客戶端,這明顯是不合理的。 所以這次采用多進程的方式來實現(xiàn)同時為多個客戶端提供服務(wù)。 以下是最終的效果:showImg(htt...

    shengguo 評論0 收藏0
  • Swoole4.x探究之多進程TCP協(xié)程服務(wù)實現(xiàn)

    摘要:有研究過框架的同學(xué)就會發(fā)現(xiàn),其實最核心的,就是用了拓展加上拓展來實現(xiàn)其底層的網(wǎng)絡(luò)服務(wù)和多進程調(diào)度。我們在模式下,測試起五個進程主進程要等待回收我們,這樣就很簡單的實現(xiàn)了一個多進程的協(xié)程服務(wù)。 有研究過Workman框架的同學(xué)就會發(fā)現(xiàn),其實workman最核心的,就是用了php socket拓展加上pcntl拓展來實現(xiàn)其底層的網(wǎng)絡(luò)服務(wù)和多進程調(diào)度。那我們今天就來探討如何使用Swoole的...

    ad6623 評論0 收藏0
  • Java編程思想之多線程(一)

    摘要:多線程技術(shù)是個很龐大的課題,編程思想這本書英文版,以下簡稱中也用了頁介紹的多線程體系。一個線程歸屬于唯一的進程,線程無法脫離進程而存在。五線程內(nèi)數(shù)據(jù)線程的私有數(shù)據(jù)僅歸屬于一個線程,不在線程之間共享,例如,,。 多線程技術(shù)是個很龐大的課題,《Java編程思想》這本書(英文版,以下簡稱TIJ)中也用了136頁介紹Java的多線程體系。的確,Java語言發(fā)展到今天,多線程機制相比其他的語言從...

    taohonghui 評論0 收藏0
  • PHP回顧之協(xié)程

    摘要:本文先回顧生成器,然后過渡到協(xié)程編程。其作用主要體現(xiàn)在三個方面數(shù)據(jù)生成生產(chǎn)者,通過返回數(shù)據(jù)數(shù)據(jù)消費消費者,消費傳來的數(shù)據(jù)實現(xiàn)協(xié)程。解決回調(diào)地獄的方式主要有兩種和協(xié)程。重點應(yīng)當(dāng)關(guān)注控制權(quán)轉(zhuǎn)讓的時機,以及協(xié)程的運作方式。 轉(zhuǎn)載請注明文章出處: https://tlanyan.me/php-review... PHP回顧系列目錄 PHP基礎(chǔ) web請求 cookie web響應(yīng) sess...

    Java3y 評論0 收藏0

發(fā)表評論

0條評論

lifesimple

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<