PHP進(jìn)程及進(jìn)程間通信

一、引言

進(jìn)程是一個(gè)具有獨(dú)立功能的程序關(guān)于某個(gè)數(shù)據(jù)集合的一次運(yùn)行活動(dòng)。換句話說(shuō)就是,在系統(tǒng)調(diào)度多個(gè)cpu的時(shí)候,一個(gè)程序的基本單元。進(jìn)程對(duì)于大多數(shù)的語(yǔ)言都不是一個(gè)陌生的概念,作為"世界上最好的語(yǔ)言PHP"當(dāng)然也例外。

二、環(huán)境

php中的進(jìn)程是以擴(kuò)展的形式來(lái)完成。通過(guò)這些擴(kuò)展,我們能夠很輕松的完成進(jìn)程的一系列動(dòng)作。

  • pcntl擴(kuò)展:主要的進(jìn)程擴(kuò)展,完成進(jìn)程創(chuàng)建于等待操作。
  • posix擴(kuò)展:完成posix兼容機(jī)通用api,如獲取進(jìn)程id,殺死進(jìn)程等。
  • sysvmsg擴(kuò)展:實(shí)現(xiàn)system v方式的進(jìn)程間通信之消息隊(duì)列。
  • sysvsem擴(kuò)展:實(shí)現(xiàn)system v方式的信號(hào)量。
  • sysvshm擴(kuò)展:實(shí)現(xiàn)system v方式的共享內(nèi)存。
  • sockets擴(kuò)展:實(shí)現(xiàn)socket通信。

這些擴(kuò)展只能在linux/mac中使用,window下是不支持。最后建議php版本為5.5+。

相關(guān)代碼:進(jìn)程相關(guān)代碼

三、簡(jiǎn)單的例子

一個(gè)簡(jiǎn)單的PHP多進(jìn)程例子,該例子中,一個(gè)子進(jìn)程,一個(gè)父進(jìn)程。子進(jìn)程輸出5次,退出程序。

$parentPid = posix_getpid();
echo "parent progress pid:{$parentPid}\n";
$childList = array();
$pid = pcntl_fork();
if ( $pid == -1) {
    // 創(chuàng)建失敗
    exit("fork progress error!\n");
} else if ($pid == 0) {
    // 子進(jìn)程執(zhí)行程序
    $pid = posix_getpid();
    $repeatNum = 5;
    for ( $i = 1; $i <= $repeatNum; $i++) {
        echo "({$pid})child progress is running! {$i} \n";
        $rand = rand(1,3);
        sleep($rand);
    }
    exit("({$pid})child progress end!\n");
} else {
    // 父進(jìn)程執(zhí)行程序
    $childList[$pid] = 1;
}
// 等待子進(jìn)程結(jié)束
pcntl_wait($status);
echo "({$parentPid})main progress end!";

完美,終于創(chuàng)建了一個(gè)子進(jìn)程,一個(gè)父進(jìn)程。完了么?沒(méi)有,各個(gè)進(jìn)程之間相互獨(dú)立的,沒(méi)有任何交集,使用范圍嚴(yán)重受到現(xiàn)在。怎么辦,哪就進(jìn)程間通信(interprogress communication)唄。

四、進(jìn)程間通信(IPC)

通常linux中的進(jìn)程通信方式有:消息隊(duì)列、信號(hào)量、共享內(nèi)存、信號(hào)、管道、socket。

1.消息隊(duì)列

消息隊(duì)列是存放在內(nèi)存中的一個(gè)隊(duì)列。如下代碼將創(chuàng)建3個(gè)生產(chǎn)者子進(jìn)程,2個(gè)消費(fèi)者子進(jìn)程。這5個(gè)進(jìn)程將通過(guò)消息隊(duì)列通信。

$parentPid = posix_getpid();
echo "parent progress pid:{$parentPid}\n";$childList = array();
// 創(chuàng)建消息隊(duì)列,以及定義消息類型(類似于數(shù)據(jù)庫(kù)中的庫(kù))
$id = ftok(__FILE__,'m');
$msgQueue = msg_get_queue($id);
const MSG_TYPE = 1;
// 生產(chǎn)者
function producer(){
    global $msgQueue;
    $pid = posix_getpid();
    $repeatNum = 5;
    for ( $i = 1; $i <= $repeatNum; $i++) {
        $str = "({$pid})progress create! {$i}";
        msg_send($msgQueue,MSG_TYPE,$str);
        $rand = rand(1,3);
        sleep($rand);
    }
}
// 消費(fèi)者
function consumer(){
    global $msgQueue;
    $pid = posix_getpid();
    $repeatNum = 6;
    for ( $i = 1; $i <= $repeatNum; $i++) {
        $rel = msg_receive($msgQueue,MSG_TYPE,$msgType,1024,$message);
        echo "{$message} | consumer({$pid}) destroy \n";
        $rand = rand(1,3);
        sleep($rand);
    }
}
function createProgress($callback){
    $pid = pcntl_fork();
    if ( $pid == -1) {
        // 創(chuàng)建失敗
        exit("fork progress error!\n");
    } else if ($pid == 0) {
        // 子進(jìn)程執(zhí)行程序
        $pid = posix_getpid();
        $callback();
        exit("({$pid})child progress end!\n");
    }else{
        // 父進(jìn)程執(zhí)行程序
        return $pid;
    }
}
// 3個(gè)寫(xiě)進(jìn)程
for ($i = 0; $i < 3; $i ++ ) {
    $pid = createProgress('producer');
    $childList[$pid] = 1;
    echo "create producer child progress: {$pid} \n";
}
// 2個(gè)寫(xiě)進(jìn)程
for ($i = 0; $i < 2; $i ++ ) {
    $pid = createProgress('consumer');
    $childList[$pid] = 1;
    echo "create consumer child progress: {$pid} \n";
}
// 等待所有子進(jìn)程結(jié)束
while(!empty($childList)){
    $childPid = pcntl_wait($status);
    if ($childPid > 0){
        unset($childList[$childPid]);
    }
}
echo "({$parentPid})main progress end!\n";

由于消息隊(duì)列去數(shù)據(jù)是,只有一個(gè)進(jìn)程能去到,所以不需要額外的鎖或信號(hào)量。

2. 信號(hào)量與共享內(nèi)存

信號(hào)量:是系統(tǒng)提供的一種原子操作,一個(gè)信號(hào)量,同時(shí)只有你個(gè)進(jìn)程能操作。一個(gè)進(jìn)程獲得了某個(gè)信號(hào)量,就必須被該進(jìn)程釋放掉。

共享內(nèi)存:是系統(tǒng)在內(nèi)存中開(kāi)辟的一塊公共的內(nèi)存區(qū)域,任何一個(gè)進(jìn)程都可以訪問(wèn),在同一時(shí)刻,可以有多個(gè)進(jìn)程訪問(wèn)該區(qū)域,為了保證數(shù)據(jù)的一致性,需要對(duì)該內(nèi)存區(qū)域加鎖或信號(hào)量。

以下,創(chuàng)建多個(gè)進(jìn)程修改內(nèi)存中的同一個(gè)值。

$parentPid = posix_getpid();
echo "parent progress pid:{$parentPid}\n";
$childList = array();

// 創(chuàng)建共享內(nèi)存,創(chuàng)建信號(hào)量,定義共享key
$shm_id = ftok(__FILE__,'m');
$sem_id = ftok(__FILE__,'s');
$shareMemory = shm_attach($shm_id);
$signal = sem_get($sem_id);
const SHARE_KEY = 1;
// 生產(chǎn)者
function producer(){
    global $shareMemory;
    global $signal;
    $pid = posix_getpid();
    $repeatNum = 5;
    for ( $i = 1; $i <= $repeatNum; $i++) {
        // 獲得信號(hào)量
        sem_acquire($signal);
        
        if (shm_has_var($shareMemory,SHARE_KEY)){
            // 有值,加一
            $count = shm_get_var($shareMemory,SHARE_KEY);
            $count ++;
            shm_put_var($shareMemory,SHARE_KEY,$count);
            echo "({$pid}) count: {$count}\n";
        }else{
            // 無(wú)值,初始化
            shm_put_var($shareMemory,SHARE_KEY,0);
            echo "({$pid}) count: 0\n";
        }
        // 用完釋放
        sem_release($signal);
        
        $rand = rand(1,3);
        sleep($rand);
    }
}
function createProgress($callback){
    $pid = pcntl_fork();
    if ( $pid == -1) {
        // 創(chuàng)建失敗
        exit("fork progress error!\n");
    } else if ($pid == 0) {
        // 子進(jìn)程執(zhí)行程序
        $pid = posix_getpid();
        $callback();
        exit("({$pid})child progress end!\n");
    }else{
        // 父進(jìn)程執(zhí)行程序
        return $pid;
    }
}
// 3個(gè)寫(xiě)進(jìn)程
for ($i = 0; $i < 3; $i ++ ) {
    $pid = createProgress('producer');
    $childList[$pid] = 1;
    echo "create producer child progress: {$pid} \n";
}
// 等待所有子進(jìn)程結(jié)束
while(!empty($childList)){
    $childPid = pcntl_wait($status);
    if ($childPid > 0){
        unset($childList[$childPid]);
    }
}
// 釋放共享內(nèi)存與信號(hào)量
shm_remove($shareMemory);
sem_remove($signal);
echo "({$parentPid})main progress end!\n";

3.信號(hào)

信號(hào)是一種系統(tǒng)調(diào)用。通常我們用的kill命令就是發(fā)送某個(gè)信號(hào)給某個(gè)進(jìn)程的。具體有哪些信號(hào)可以在liunx/mac中運(yùn)行kill -l查看。下面這個(gè)例子中,父進(jìn)程等待5秒鐘,向子進(jìn)程發(fā)送sigint信號(hào)。子進(jìn)程捕獲信號(hào),掉信號(hào)處理函數(shù)處理。


$parentPid = posix_getpid();
echo "parent progress pid:{$parentPid}\n";

// 定義一個(gè)信號(hào)處理函數(shù)
function sighandler($signo) {
    $pid = posix_getpid();
    echo "{$pid} progress,oh no ,I'm killed!\n";
    exit(1);
}

$pid = pcntl_fork();
if ( $pid == -1) {
    // 創(chuàng)建失敗
    exit("fork progress error!\n");
} else if ($pid == 0) {
    // 子進(jìn)程執(zhí)行程序
    // 注冊(cè)信號(hào)處理函數(shù)
    declare(ticks=10);
    pcntl_signal(SIGINT, "sighandler");
    $pid = posix_getpid();
    while(true){
        echo "{$pid} child progress is running!\n";
        sleep(1);
    }
    exit("({$pid})child progress end!\n");
}else{
    // 父進(jìn)程執(zhí)行程序
    $childList[$pid] = 1;
    // 5秒后,父進(jìn)程向子進(jìn)程發(fā)送sigint信號(hào).
    sleep(5);
    posix_kill($pid,SIGINT);
    sleep(5);
}
echo "({$parentPid})main progress end!\n";

4.管道(有名管道)

管道是比較常用的多進(jìn)程通信手段,管道分為無(wú)名管道與有名管道,無(wú)名管道只能用于具有親緣關(guān)系的進(jìn)程間通信,而有名管道可以用于同一主機(jī)上任意進(jìn)程。這里只介紹有名管道。下面的例子,子進(jìn)程寫(xiě)入數(shù)據(jù),父進(jìn)程讀取數(shù)據(jù)。

// 定義管道路徑,與創(chuàng)建管道
$pipe_path = '/data/test.pipe';
if(!file_exists($pipe_path)){
    if(!posix_mkfifo($pipe_path,0664)){
        exit("create pipe error!");
    }
}
$pid = pcntl_fork();
if($pid == 0){
    // 子進(jìn)程,向管道寫(xiě)數(shù)據(jù)
    $file = fopen($pipe_path,'w');
    while (true){
        fwrite($file,'hello world');
        $rand = rand(1,3);
        sleep($rand);
    }
    exit('child end!');
}else{
    // 父進(jìn)程,從管道讀數(shù)據(jù)
    $file = fopen($pipe_path,'r');
    while (true){
        $rel = fread($file,20);
        echo "{$rel}\n";
        $rand = rand(1,2);
        sleep($rand);
    }
}

5.socket

socket即我們常說(shuō)的套接字編程。這個(gè)待補(bǔ)充。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容