深入探究PHP的多進(jìn)程編程方法
子進(jìn)程的創(chuàng)建
一般的子進(jìn)程的寫法是:
<?php $pid = pcntl_fork(); if($pid == -1){ //創(chuàng)建失敗 die('could not fork'); } else{ if($pid){ //從這里開始寫的代碼是父進(jìn)程的 exit("parent!"); } else{ //子進(jìn)程代碼,為防止不停的啟用子進(jìn)程造成系統(tǒng)資源被耗盡的情況,一般子進(jìn)程代碼運(yùn)行完成后,加入exit來確保子進(jìn)程正常退出。 exit("child"); } } ?>
上邊的代碼如果創(chuàng)建子進(jìn)程成功的話,系統(tǒng)就有了2個(gè)進(jìn)程,一個(gè)為父進(jìn)程,一個(gè)為子進(jìn)程,子進(jìn)程的id號(hào)為$pid。在系統(tǒng)運(yùn)行到$pid = pcntl_fork();時(shí),在這個(gè)地方進(jìn)行分支,父子進(jìn)程各自開始運(yùn)行各自的程序代碼。代碼的運(yùn)行結(jié)果是parent 和child,很奇怪吧,為什么一個(gè)if和else互斥的代碼中,都輸出了結(jié)果?其實(shí)是像上邊所說的,代碼在pcntl_fork時(shí),一個(gè)父進(jìn)程運(yùn)行parent,一個(gè)子進(jìn)程運(yùn)行了child。在代碼結(jié)果上就顯示了parent和child。至于誰先誰后的問題,這得要看系統(tǒng)資源的分配了。
如果需要起多個(gè)進(jìn)程來處理數(shù)據(jù),可以根據(jù)數(shù)據(jù)的數(shù)量,按照約定好的數(shù)量比如說1000條一個(gè)進(jìn)程來起子進(jìn)程。使用for循環(huán)就可以了。
#如果獲得的總數(shù)小于或等于0,等待60秒,并退出 if ($count <= 0) { sleep(60); exit; } #如果大于1000,計(jì)算需要起的進(jìn)程數(shù) if ($count > 1000) { $cycleSize = ceil($count/1000); } else { $cycleSize = 1; } for ($i=0; $i<$cycleSize; $i++) { $pid = pcntl_fork(); if($pid == -1) { break; } else { if($pid) { #父進(jìn)程獲得子進(jìn)程的pid,存入數(shù)組 $pidArr[] = $pid; } else { //開始發(fā)送,子進(jìn)程執(zhí)行完自己的任務(wù)后,退出。 exit; } } } while(count($pidArr) > 0) { $myId = pcntl_waitpid(-1, $status, WNOHANG); foreach($pidArr as $key => $pid) { if($myId == $pid) unset($pidArr[$key]); } }
然后使用crontab,來使此PHP程序每隔一段時(shí)間自動(dòng)執(zhí)行。
當(dāng)然,示例代碼比較簡單,具體還需要考慮怎么防止多個(gè)子進(jìn)程執(zhí)行到同一條數(shù)據(jù)或者當(dāng)前進(jìn)程處理數(shù)據(jù)未完成時(shí),crontab又開始執(zhí)行PHP文件啟用新的進(jìn)程等等。
PHP多進(jìn)程實(shí)現(xiàn)方式
下面來系統(tǒng)地整理一下PHP多進(jìn)程的實(shí)現(xiàn)方式:
1. 直接方式
pcntl_fork() 創(chuàng)建一個(gè)進(jìn)程,在父進(jìn)程返回值是子進(jìn)程的pid,在子進(jìn)程返回值是0,-1表示創(chuàng)建進(jìn)程失敗。跟C非常相似。
測試腳本 test.php
<?php // example of multiple processes date_default_timezone_set( 'Asia/Chongqing'); echo "parent start, pid ", getmypid(), "\n" ; beep(); for ($i=0; $i<3; ++$i){ $pid = pcntl_fork(); if ($pid == -1){ die ("cannot fork" ); } else if ($pid > 0){ echo "parent continue \n"; for ($k=0; $k<2; ++$k){ beep(); } } else if ($pid == 0){ echo "child start, pid ", getmypid(), "\n" ; for ($j=0; $j<5; ++$j){ beep(); } exit ; } } // *** function beep(){ echo getmypid(), "\t" , date( 'Y-m-d H:i:s', time()), "\n" ; sleep(1); } ?>
用命令行運(yùn)行
#php -f test.php
輸出結(jié)果
parent start, pid 1793 1793 2013-01-14 15:04:17 parent continue 1793 2013-01-14 15:04:18 child start, pid 1794 1794 2013-01-14 15:04:18 1794 2013-01-14 15:04:19 1793 2013-01-14 15:04:19 1794 2013-01-14 15:04:20 parent continue 1793 2013-01-14 15:04:20 child start, pid 1795 1795 2013-01-14 15:04:20 17931794 2013-01-14 15:04:212013-01-14 15:04:21 1795 2013-01-14 15:04:21 1794 2013-01-14 15:04:22 1795 2013-01-14 15:04:22 parent continue 1793 2013-01-14 15:04:22 child start, pid 1796 1796 2013-01-14 15:04:22 1793 2013-01-14 15:04:23 1796 2013-01-14 15:04:23 1795 2013-01-14 15:04:23 1795 2013-01-14 15:04:24 1796 2013-01-14 15:04:24 1796 2013-01-14 15:04:25 1796 2013-01-14 15:04:26
從中看到,創(chuàng)建了3個(gè)子進(jìn)程,和父進(jìn)程一起并行運(yùn)行。其中有一行格式跟其他有些不同,
17931794 2013-01-14 15:04:212013-01-14 15:04:21
因?yàn)閮蓚€(gè)進(jìn)程同時(shí)進(jìn)行寫操作,造成了沖突。
2. 阻塞方式
用直接方式,父進(jìn)程創(chuàng)建了子進(jìn)程后,并沒有等待子進(jìn)程結(jié)束,而是繼續(xù)運(yùn)行。似乎這里看不到有什么問題。如果php腳本并不是運(yùn)行完后自動(dòng)結(jié)束,而是常駐內(nèi)存的,就會(huì)造成子進(jìn)程無法回收的問題。也就是僵尸進(jìn)程??梢酝ㄟ^pcntl_wai()方法等待進(jìn)程結(jié)束,然后回收已經(jīng)結(jié)束的進(jìn)程。
將測試腳本改成:
$pid = pcntl_fork(); if ($pid == -1){ ... } else if ($pid > 0){ echo "parent continue \n"; pcntl_wait($status); for ($k=0; $k<2; ++$k){ beep(); } } else if ($pid == 0){ ... }
用命令行運(yùn)行
#php -f test.php
輸出結(jié)果
parent start, pid 1807 1807 2013-01-14 15:20:05 parent continue child start, pid 1808 1808 2013-01-14 15:20:06 1808 2013-01-14 15:20:07 1808 2013-01-14 15:20:08 1808 2013-01-14 15:20:09 1808 2013-01-14 15:20:10 1807 2013-01-14 15:20:11 1807 2013-01-14 15:20:12 parent continue child start, pid 1809 1809 2013-01-14 15:20:13 1809 2013-01-14 15:20:14 1809 2013-01-14 15:20:15 1809 2013-01-14 15:20:16 1809 2013-01-14 15:20:17 1807 2013-01-14 15:20:18 1807 2013-01-14 15:20:19 child start, pid 1810 1810 2013-01-14 15:20:20 parent continue 1810 2013-01-14 15:20:21 1810 2013-01-14 15:20:22 1810 2013-01-14 15:20:23 1810 2013-01-14 15:20:24 1807 2013-01-14 15:20:25 1807 2013-01-14 15:20:26
父進(jìn)程在pcntl_wait()將自己阻塞,等待子進(jìn)程運(yùn)行完了才接著運(yùn)行。
3. 非阻塞方式
阻塞方式失去了多進(jìn)程的并行性。還有一種方法,既可以回收已經(jīng)結(jié)束的子進(jìn)程,又可以并行。這就是非阻塞的方式。
修改腳本:
<?php // example of multiple processes date_default_timezone_set( 'Asia/Chongqing'); declare (ticks = 1); pcntl_signal(SIGCHLD, "garbage" ); echo "parent start, pid ", getmypid(), "\n" ; beep(); for ($i=0; $i<3; ++$i){ $pid = pcntl_fork(); if ($pid == -1){ die ("cannot fork" ); } else if ($pid > 0){ echo "parent continue \n"; for ($k=0; $k<2; ++$k){ beep(); } } else if ($pid == 0){ echo "child start, pid ", getmypid(), "\n" ; for ($j=0; $j<5; ++$j){ beep(); } exit (0); } } // parent while (1){ // do something else sleep(5); } // *** function garbage($signal){ echo "signel $signal received\n" ; while (($pid = pcntl_waitpid(-1, $status, WNOHANG))> 0){ echo "\t child end pid $pid , status $status\n" ; } } function beep(){ echo getmypid(), "\t" , date( 'Y-m-d H:i:s', time()), "\n" ; sleep(1); } ?>
用命令行運(yùn)行
#php -f test.php &
輸出結(jié)果
parent start, pid 2066 2066 2013-01-14 16:45:34 parent continue 2066 2013-01-14 16:45:35 child start, pid 2067 2067 2013-01-14 16:45:35 20662067 2013-01-14 16:45:362013-01-14 16:45:36 2067 2013-01-14 16:45:37 parent continue 2066 2013-01-14 16:45:37 child start, pid 2068 2068 2013-01-14 16:45:37 2067 2013-01-14 16:45:38 2068 2013-01-14 16:45:38 2066 2013-01-14 16:45:38 parent continue 2066 2013-01-14 16:45:40 child start, pid 2069 2069 2067 2013-01-14 16:45:40 2013-01-14 16:45:40 2068 2013-01-14 16:45:40 2066 2013-01-14 16:45:41 2069 2013-01-14 16:45:41 2068 2013-01-14 16:45:41 signel 17 received child end pid 2067, status 0 2069 2013-01-14 16:45:42 2068 2013-01-14 16:45:42 2069 2013-01-14 16:45:43 signel 17 received child end pid 2068, status 0 2069 2013-01-14 16:45:44 signel 17 received child end pid 2069, status 0
多個(gè)進(jìn)程又并行運(yùn)行了,而且運(yùn)行大約10秒鐘之后,用 ps -ef | grep php 查看正在運(yùn)行的進(jìn)程,只有一個(gè)進(jìn)程
lqling 2066 1388 0 16:45 pts/1 00:00:00 php -f t5.php
是父進(jìn)程,子進(jìn)程被回收了。
子進(jìn)程退出狀態(tài)
pcntl_waitpid(-1, $status, WNOHANG) $status
返回子進(jìn)程的結(jié)束狀態(tài)
windows下多線程
windows系統(tǒng)不支持pcntl函數(shù),幸好有curl_multi_exec()這個(gè)工具,利用內(nèi)部的多線程,訪問多個(gè)鏈接,每個(gè)鏈接可以作為一個(gè)任務(wù)。
編寫腳本 test1.php
<?php date_default_timezone_set( 'Asia/Chongqing'); $tasks = array( 'http://localhost/feedbowl/t2.php?job=task1', 'http://localhost/feedbowl/t2.php?job=task2', 'http://localhost/feedbowl/t2.php?job=task3' ); $mh = curl_multi_init(); foreach ($tasks as $i => $task){ $ch[$i] = curl_init(); curl_setopt($ch[$i], CURLOPT_URL, $task); curl_setopt($ch[$i], CURLOPT_RETURNTRANSFER, 1); curl_multi_add_handle($mh, $ch[$i]); } do {$mrc = curl_multi_exec($mh,$active); } while ($mrc == CURLM_CALL_MULTI_PERFORM); while ($active && $mrc == CURLM_OK) { if (curl_multi_select($mh) != -1) { do {$mrc = curl_multi_exec($mh, $active); } while ($mrc == CURLM_CALL_MULTI_PERFORM); } } // completed, checkout result foreach ($tasks as $j => $task){ if (curl_error($ch[$j])){ echo "task ${j} [$task ] error " , curl_error($ch[$j]), "\r\n" ; } else { echo "task ${j} [$task ] get: \r\n" , curl_multi_getcontent($ch[$j]), "\r\n" ; } } ?>
編寫腳本 test2.php
<?php date_default_timezone_set( 'Asia/Chongqing'); echo "child start, pid ", getmypid(), "\r\n" ; for ($i=0; $i<5; ++$i){ beep(); } exit (0); // *** function beep(){ echo getmypid(), "\t" , date('Y-m-d H:i:s' , time()), "\r\n"; sleep(1); } ?>
用命令行運(yùn)行
#php -f test1.php &
輸出結(jié)果
task 0 [http://localhost/feedbowl/t2.php?job=task1] get: child start, pid 5804 5804 2013-01-15 20:22:35 5804 2013-01-15 20:22:36 5804 2013-01-15 20:22:37 5804 2013-01-15 20:22:38 5804 2013-01-15 20:22:39 task 1 [http://localhost/feedbowl/t2.php?job=task2] get: child start, pid 5804 5804 2013-01-15 20:22:35 5804 2013-01-15 20:22:36 5804 2013-01-15 20:22:37 5804 2013-01-15 20:22:38 5804 2013-01-15 20:22:39 task 2 [http://localhost/feedbowl/t2.php?job=task3] get: child start, pid 5804 5804 2013-01-15 20:22:35 5804 2013-01-15 20:22:36 5804 2013-01-15 20:22:37 5804 2013-01-15 20:22:38 5804 2013-01-15 20:22:39
從打印的時(shí)間看到,多個(gè)任務(wù)幾乎是同時(shí)運(yùn)行的。
相關(guān)文章
PHP中一個(gè)有趣的preg_replace函數(shù)詳解
這篇文章主要給大家介紹了關(guān)于PHP中一個(gè)有趣的preg_replace函數(shù)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用php具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-08-08mac下Apache + MySql + PHP搭建網(wǎng)站開發(fā)環(huán)境
最近接了個(gè)小活,做一個(gè)使用PHP語言和MySql數(shù)據(jù)庫的動(dòng)態(tài)網(wǎng)站。之前做過類型的網(wǎng)站,是在windows系統(tǒng)下做的,開發(fā)環(huán)境使用的是 AppServ 的PHP開發(fā)套件?,F(xiàn)在有了我的大MAC,所以找了MAC系統(tǒng)下PHP環(huán)境的開發(fā)套件。2014-06-06對PHP PDO的一些認(rèn)識(shí)小結(jié)
這篇文章主要介紹了對PHP PDO的一些認(rèn)識(shí)小結(jié),本文講解了什么是PDO、啟用PDO的配置方法、PDO的預(yù)定義類、事務(wù)處理例子等內(nèi)容,需要的朋友可以參考下2015-01-01