PHP在多線程支持方面一直被人詬病编丘,其實(shí)PHP可以支持多線程,不過需要單獨(dú)安裝擴(kuò)展模塊彤悔;
另外相比于多線程嘉抓,其實(shí)多進(jìn)程模塊使用方面比多線程要更友好一些,如果你想在PHP中體驗(yàn)多進(jìn)程OR多線程晕窑,那就跟隨村長開始吧抑片!
PHP多進(jìn)程
一般涉及兩個(gè)函數(shù), pcntl_fork, pcntl_wait
;
pcntl_fork
用于創(chuàng)建子進(jìn)程杨赤,會(huì)在在當(dāng)前進(jìn)程當(dāng)前位置產(chǎn)生分叉(所以取名fork)敞斋;
這個(gè)子進(jìn)程僅 PID(進(jìn)程號(hào)) 和 PPID(父進(jìn)程號(hào))與其父進(jìn)程不同;子進(jìn)程的PID為0疾牲,PPID 為當(dāng)前腳本執(zhí)行時(shí)的進(jìn)程號(hào)植捎。
成功創(chuàng)建時(shí),在父進(jìn)程執(zhí)行線程內(nèi)返回產(chǎn)生的子進(jìn)程的 PID说敏,在子進(jìn)程執(zhí)行線程內(nèi)返回 0鸥跟。
失敗時(shí),在 父進(jìn)程上下文返回 -1盔沫,不會(huì)創(chuàng)建子進(jìn)程医咨,并且會(huì)引發(fā) PHP 錯(cuò)誤。
pcntl_wait
等待或返回 fork 的子進(jìn)程狀態(tài), 注:該方法會(huì)阻塞當(dāng)前進(jìn)程的執(zhí)行架诞;
執(zhí)行該函數(shù)時(shí)拟淮,會(huì)掛起當(dāng)前進(jìn)程的執(zhí)行,直到一個(gè)子進(jìn)程退出或接收到一個(gè)信號(hào)要求中斷當(dāng)前進(jìn)程或調(diào)用一個(gè)信號(hào)處理函數(shù)谴忧;
如果一個(gè)子進(jìn)程在調(diào)用此函數(shù)時(shí)已退出(俗稱僵尸進(jìn)程)很泊,此函數(shù)會(huì)立刻返回角虫,子進(jìn)程使用的所有系統(tǒng)資源將被釋放;
執(zhí)行順序:
- 1.程序執(zhí)行時(shí)委造,從外到內(nèi)創(chuàng)建fork戳鹅,再從內(nèi)層最后一次fork開始依次退出;
- 2.如一次fork之后昏兆,其父進(jìn)程因 pcntl_wait 阻塞枫虏,會(huì)等待之前已被執(zhí)行的【任意fork子進(jìn)程】退出后,再執(zhí)行后面的邏輯爬虱;
- 3.執(zhí)行本子進(jìn)程的父進(jìn)程依次循環(huán)步驟【2】的邏輯直到退出隶债,最終結(jié)束總進(jìn)程;
參考示例:
$maxLoop = 11; // 最大允許的folk次數(shù)跑筝,超過此數(shù)量后直接退出
$idxStart = 0; // folk次數(shù)-計(jì)數(shù)器
$maxcnt = 3; // 最大可允許的同時(shí)創(chuàng)建的進(jìn)程數(shù)死讹,可理解為 進(jìn)程并發(fā)量;
$cnt = 0; // 已創(chuàng)建的進(jìn)程 計(jì)數(shù)器曲梗;
while (1) {
$pid = pcntl_fork();
if($pid===-1){
die('folk err...');
} else if($pid) {
$idxStart++; // folk次數(shù)-計(jì)數(shù)器
$cnt++;
// 控制最大并發(fā)執(zhí)行進(jìn)程數(shù)赞警,超過此數(shù)量,等待 任一子進(jìn)程退出 后再執(zhí)行稀并;
if($cnt >= $maxcnt){
// pcntl_wait 里的 status 參數(shù)是引用傳遞仅颇,PHP 會(huì)存儲(chǔ)狀態(tài)信息到 status 參數(shù)上,通過 status 參數(shù)可監(jiān)測(cè)當(dāng)前進(jìn)程是否正常執(zhí)行完退出等等碘举,這里不作詳細(xì)解釋忘瓦,感興趣的可直接參考PHP官網(wǎng);
pcntl_wait($status); // 防止僵尸子進(jìn)程
echo PHP_EOL, "Iam waiting: $cnt", PHP_EOL;
print_r([$idxStart, $status]);
echo PHP_EOL;
if($idxStart>$maxLoop) {
// 超過最大允許的folk次數(shù)引颈,退出耕皮!
echo PHP_EOL, "loop-num: {$idxStart}, stop...", PHP_EOL;
exit(0);
}
$cnt--;
}
} else {
sleep(1);
echo PHP_EOL, "curr-i: $cnt", PHP_EOL;
exit(0); // 避免子進(jìn)程中執(zhí)行foreach循環(huán)
// 或者使用下面的方法,直接-kill
posix_kill(getmypid(),9); // Instead of calling exit(), we use posix_kill()
}
}
第二種調(diào)用方式
function mainExec()
{
$count = 6;
for ($i = 1; $i <= $count; $i++) {
$pid = pcntl_fork();
if ($pid > 0) {
// 主進(jìn)程相關(guān)操作...
} else if (0 == $pid) {
task($i);
exit; // 一定要退出蝙场,否則子進(jìn)程會(huì)繼承上下文繼續(xù)執(zhí)行
} else {
echo "fork失敗" . PHP_EOL;
}
}
// sleep(10); // 放開此方法凌停,會(huì)出現(xiàn)僵尸進(jìn)程
while ($count) {
// 注意,pcntl_wait 是可以脫離循環(huán)主體獨(dú)立執(zhí)行的售滤;
$pid = pcntl_wait($result);
var_dump($pid);
var_dump($result);
$count--;
}
}
function task($taskId)
{
// 此處罚拟,多進(jìn)程執(zhí)行相關(guān)邏輯。完箩。赐俗。
echo "task {$taskId} was done..", PHP_EOL;
}
mainExec();