PHP多進程

PHP多進程

1.多開幾個進程,這種方式簡單實用垦细,推薦,比如說使用shell腳本:

#!/bin/bash
 
for((i=1;i<=8;i++))
do    
    /usr/bin/php multiprocessTest.php &
done
 
wait

2.pcntl擴展

php多進程需要pcntl挡逼,posix擴展支持括改,可以通過 php -m 查看,而且多進程實現(xiàn)只能在cli模式下家坎,雖然是個殘廢嘱能,不妨也了解一下, 實際上這些都是調(diào)用了Linux的系統(tǒng)API
舉個例子:

<?php
foreach (range(1, 5) as $index) {
    $pid = pcntl_fork();
    if ($pid === -1) {
        echo "failed to fork!\n";
        exit;
    } elseif ($pid) {
        pcntl_wait($status); //父進程必須等待一個子進程退出后,再創(chuàng)建下一個子進程虱疏。
        echo "I am the parent, pid: $pid\n";
    } else {
        $cid = posix_getpid();
        echo "fork the {$index}th child, pid: $cid\n";
        exit; //必須
    }
}

這個例子非常簡單惹骂,循環(huán)創(chuàng)建5個進程,在各個進程里面打印一句話做瞪,主要使用的方法就是函數(shù) pcntl_fork对粪,一次調(diào)用兩次返回,在父進程中返回子進程pid装蓬,在子進程中返回0著拭,出錯返回-1。

執(zhí)行結(jié)果如下:

fork the 1th child, pid: 7326
I am the parent, pid: 7326
fork the 2th child, pid: 7327
I am the parent, pid: 7327
fork the 3th child, pid: 7328
I am the parent, pid: 7328
fork the 4th child, pid: 7329
I am the parent, pid: 7329
fork the 5th child, pid: 7330
I am the parent, pid: 7330

先解釋一下為什么會產(chǎn)生10條打印結(jié)果牍帚,第一條結(jié)果是子進程打印的儡遮,第二條是在父進程打印的!

第一個坑:

如果是在循環(huán)中創(chuàng)建子進程,那么子進程中最后要exit,防止子進程進入循環(huán)!

第二個坑:

必須等待子進程執(zhí)行完任務(wù), 有一個簡單方法是使用 pcntl_wait暗赶,如果不加這個你會發(fā)現(xiàn)一個是執(zhí)行的順序不固定旨涝,第二個就是創(chuàng)建的進程會少于5個,但是加了你會發(fā)現(xiàn)這個完全變成并行了...上面的結(jié)果就是

然后找了找踏堡,發(fā)現(xiàn)下面這種寫法:

<?php

$ids = [];

foreach (range(1, 5) as $index) {
    $ids[] = $pid = pcntl_fork();
    if ($pid === -1) {
        echo "failed to fork!\n";
        exit;
    } elseif ($pid) {
        echo "I am the parent, pid: $pid\n";
    } else {
        $cid = posix_getpid();
        echo "fork the {$index}th child, pid: $cid\n";
        exit;
    }
}

foreach ($ids as $i => $pid) {
    if ($pid) {
        pcntl_waitpid($pid, $status);
    }
}

結(jié)果如下:

fork the 1th child, pid: 8392
I am the parent, pid: 8392
I am the parent, pid: 8393
fork the 2th child, pid: 8393
I am the parent, pid: 8394
I am the parent, pid: 8395
I am the parent, pid: 8396
fork the 3th child, pid: 8394
fork the 4th child, pid: 8395
fork the 5th child, pid: 8396

找了一張圖,大體解釋了總體流程:

image

說簡單其實也挺簡單,幾行代碼就可以寫出一個多進程程序,實現(xiàn)并行編程蓝翰,但是這里其實還有不少坑,比如僵尸進程模孩,孤兒進程, 守護進程厌小,具體的我也不太熟悉不多講,再看一個關(guān)于進程信號的東西浸锨,有些項目里面有時候會用到一些腳本唇聘,比如處理redis隊列的腳本,通常的做法是寫一個while死循環(huán)一直從redis里面取數(shù)據(jù)處理柱搜,為了防止內(nèi)存泄露或者假死迟郎,一般都會定時的殺掉腳本重啟腳本,但是殺的不好可能會導(dǎo)致數(shù)據(jù)丟失聪蘸,舉個例子宪肖,假如你這個腳本剛好從redis取了一條數(shù)據(jù)正在處理中表制,操作還未完成,你突然終止進程控乾,那這個數(shù)據(jù)就丟失了么介。至于說服務(wù)器掛掉這種情況畢竟不多見,真要解決這種問題還得從隊列上入手蜕衡。

<?php

//ctrl+c
pcntl_signal(SIGINT, function () {
    fwrite(STDOUT, "receive signal: " . SIGINT . " do nothing ...\n");
});

//kill
pcntl_signal(SIGTERM, function () {
    fwrite(STDOUT, "receive signal: " . SIGTERM . " I will exit!\n");
    exit;
});

while (true) {
    pcntl_signal_dispatch();
    echo "do something壤短。。慨仿。\n";
    sleep(5);
}
image

Linux進程信號分為很多種久脯,kill -l 可以查看,PHP里面定義了43種镰吆,咱就說說常用的幾種:

SIGINT 2 這個其實相對于 ctrl+c

SIGTERM 15 就是 kill 默認的參數(shù)帘撰,表示終止信號,但是你發(fā)了信號程序不一定響應(yīng)

SIGKILL 9 就是 kill -9, 表示立馬終止万皿,這個信號在PHP里面是無法注冊的骡和,所以一定能成功

看明白了這個就可以讀懂上面的例子了,其中 pcntl_signal 是注冊信號處理handler相寇,第一個參數(shù)是你需要注冊的信號慰于,第二個是處理操作,可以是匿名函數(shù)或者一個函數(shù)名唤衫,可以注冊多個信號婆赠。pcntl_signal_dispatch 調(diào)用每個等待信號通過pcntl_signal() 安裝的處理器。早期PHP還有一種寫法是使用 ticks佳励,性能非常差休里,php5.3之后建議都使用 pcntl_signal_dispatch。

說明一下:pcntl_signal()函數(shù)僅僅是注冊信號和它的處理方法赃承,真正接收到信號并調(diào)用其處理方法的是pcntl_signal_dispatch()函數(shù)必須在循環(huán)里調(diào)用妙黍,為了檢測是否有新的信號等待dispatching。

上面的例子執(zhí)行結(jié)果就是當你使用 ctrl+c 的話是無法終止程序的瞧剖,只有使用 kill pid 這種形式才可以拭嫁,但是并不是立馬就退出,它是代碼執(zhí)行到循環(huán)頂部 pcntl_signal_dispatch 地方的時候才會退出抓于,這就保證了你使用kill殺掉進程的時候并不會丟失數(shù)據(jù)做粤,說好聽點這也算是平滑重啟吧!

由于進程的系統(tǒng)開銷比較大捉撮,一般不太適合拿來做大規(guī)模并發(fā)程序怕品,拿來寫個3-5個進程的后臺腳本倒是有點用,下面就是我寫的一個用來爬取xhprof的數(shù)據(jù)的腳本巾遭,使用了3個進程同時爬取實戰(zhàn)肉康,路徑闯估,免費課的日志然后做統(tǒng)計根據(jù)出現(xiàn)次數(shù)排序!

<?php
define("TOTAL_PAGE", 100);   //總共多少頁
define("MS", 2000);          //毫秒
define("DAY", 3);            //幾天內(nèi)
define("SAVE_DIR", "/home/jwang");   //保存目錄

$servers = [
    'mkw' => '10.100.133.99',
    'sz'  => '10.100.135.23',
    'lj'  => '10.100.17.13',
];

$ids = [];

foreach ($servers as $key => $server) {
    $ids[] = $pid = pcntl_fork();
    if ($pid === -1) {
        echo "failed to fork!\n";
        exit;
    } elseif ($pid) {
    } else {
        download($server, $key);
    }
}

foreach ($ids as $i => $pid) {
    if ($pid) {
        pcntl_waitpid($pid, $status);
    }
}

function download($server, $fileName)
{
    $saveDir = SAVE_DIR;
    if (!is_dir(SAVE_DIR)) {
        $saveDir = __DIR__;
    }

    $file = $saveDir . "/xhprof_{$fileName}_tmp.txt";
    $fp   = fopen($file, 'w+');
    foreach (range(1, TOTAL_PAGE) as $page) {
        print_r("### " . date('Y-m-d H:i:s') . ": 正在爬取 $server -> $fileName 第 $page 頁...\n");
        try {
            $html = file_get_contents("http://{$server}/xhprof/index.php?page={$page}&ms=" . MS . "&day=" . DAY);
        } catch (Exception $exception) {
            var_dump("網(wǎng)絡(luò)請求失敗!\n");
            exit;
        }
        if (!$html) {
            var_dump("網(wǎng)絡(luò)請求失敗!\n");
            exit;
        }

        preg_match_all("/<a .*>(.*)<\\/a>/", $html, $matches);
        if (isset($matches[1])) {
            if (count($matches[1]) <= 3) {
                break;
            }
            foreach ($matches[1] as $match) {
                fwrite($fp, $match . "\n");
            }
        }
    }
    fclose($fp);
    print_r("### " . date('Y-m-d H:i:s') . ": 爬取完成吼和,開始處理數(shù)據(jù)...\n");
    print_r("---------------------------------------------------------- \n");
    $fp = file($file);
    if (!$fp) {
        var_dump("文件讀取失敗!\n");
    }

    foreach ($fp as $key => $item) {
        $item = rtrim(parse_url(trim($item))['path'], "/");
        if (substr($item, 0, 1) != '/') {
            unset($fp[$key]);
            continue;
        }
        $fp[$key] = preg_replace("/\/\d+/", "/*", $item);
    }

    $res = array_count_values($fp);

    uasort($res, function ($a, $b) {
        return $a < $b;
    });
    $saveFile = fopen($saveDir . "/xhprof_{$fileName}.txt", 'w+');

    foreach ($res as $key => $value) {
        $key = trim($key);
        $str = sprintf("%-50s ===============> %s 次\n", $key, $value);
        fwrite($saveFile, $str);
    }

    fclose($saveFile);
    unlink($file);
    exit;
}

最后還忘記說了一個坑涨薪,在子進程里面使用mysql 或者 redis 這類程序有個bug,假如你使用的是單例模式的話纹安,這個連接被多個子進程使用就會出問題,所以如果要使用砂豌,必須在各個子進程內(nèi)部新建一個連接厢岂!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市阳距,隨后出現(xiàn)的幾起案子塔粒,更是在濱河造成了極大的恐慌,老刑警劉巖筐摘,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件卒茬,死亡現(xiàn)場離奇詭異,居然都是意外死亡咖熟,警方通過查閱死者的電腦和手機圃酵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來馍管,“玉大人郭赐,你說我怎么就攤上這事∪贩校” “怎么了捌锭?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長罗捎。 經(jīng)常有香客問我观谦,道長,這世上最難降的妖魔是什么桨菜? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任豁状,我火速辦了婚禮,結(jié)果婚禮上倒得,老公的妹妹穿的比我還像新娘替蔬。我一直安慰自己,他們只是感情好屎暇,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布承桥。 她就那樣靜靜地躺著,像睡著了一般根悼。 火紅的嫁衣襯著肌膚如雪凶异。 梳的紋絲不亂的頭發(fā)上蜀撑,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機與錄音剩彬,去河邊找鬼酷麦。 笑死,一個胖子當著我的面吹牛喉恋,可吹牛的內(nèi)容都是我干的沃饶。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼轻黑,長吁一口氣:“原來是場噩夢啊……” “哼糊肤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起氓鄙,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤馆揉,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后抖拦,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體升酣,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年态罪,在試婚紗的時候發(fā)現(xiàn)自己被綠了噩茄。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡复颈,死狀恐怖巢墅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情券膀,我是刑警寧澤君纫,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站芹彬,受9級特大地震影響蓄髓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜舒帮,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一会喝、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧玩郊,春花似錦肢执、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至侦厚,卻和暖如春耻陕,著一層夾襖步出監(jiān)牢的瞬間拙徽,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工诗宣, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留膘怕,地道東北人。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓召庞,卻偏偏與公主長得像岛心,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子篮灼,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容