這兩天在研究php模擬多線程的問(wèn)題,碰到一個(gè)問(wèn)題就是無(wú)論exec、popen、還是proc_open都會(huì)造成等待,也就是阻塞式的調(diào)用,而我想要得是無(wú)阻塞的調(diào)用,讓程序在后臺(tái)執(zhí)行就可以解決問(wèn)題,搜索之后找到了解決辦法:
<?php /* Note that the call itself isn‘t sanitized in any way, so it‘s important to make sure that this can‘t be exploited, see docs for escapeshellcmd() for details */ // Demonstration if(launchBackgroundProcess(‘touch testfile.txt‘)){ print ‘Successfully launched background process‘; } /** * Launch Background Process * * Launches a background process (note, provides no security itself, $call must be sanitized prior to use) * @param string $call the system call to make * @author raccettura */ function launchBackgroundProcess($call) { // Windows if(is_windows()){ pclose(popen(‘start /b ‘.$call.‘‘, ‘r‘)); } // Some sort of UNIX else { pclose(popen($call.‘ /dev/null &‘, ‘r‘)); } return true; } /** * Is Windows * * Tells if we are running on Windows Platform * @author raccettura */ function is_windows(){ if(PHP_OS == ‘WINNT‘ || PHP_OS == ‘WIN32‘){ return true; } return false; } ?>
關(guān)鍵在于
‘ /dev/null &‘
(*nix下的后臺(tái)運(yùn)行方式)
和
‘start /b ‘
(windows下的后臺(tái)運(yùn)行方式)
想了一下,似乎很多腳本都可以用這種方式將一些操作移交后臺(tái)進(jìn)行,可以大幅提高效率。
如果要返回結(jié)果,則可以用異步執(zhí)行并返回狀態(tài)的方式,邊處理邊顯示處理結(jié)果,這可以用在一些耗時(shí)較長(zhǎng)的操作上,比如論壇備份\恢復(fù)等:
<?php /**** * Again notice this is unsanitized since we trust ourselves. Coming from the web * it would need to be sanitized to ensure it‘s safe to use. see escapeshellarg() ****/ $hostname = ‘a(chǎn)ccettura.com‘; ?> <!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www./TR/xhtml11/DTD/xhtml11.dtd"> <HTML xmlns="http://www./1999/xhtml"> <head> <title>Traceroute to $hostname</title> </head> <body> <?php if(is_windows()){ $cmd = ‘tracert -w 10‘; } else { $cmd = ‘traceroute -w 10‘; } $handle = popen("$cmd $hostname 2>&1", ‘r‘); while(!feof($handle)) { $buffer = fgets($handle); $buffer = ‘<p>‘.trim(htmlspecialchars($buffer)).‘</p>‘; echo $buffer; ob_flush(); flush(); } pclose($handle); /** * Is Windows * * Tells if we are running on Windows Platform * @author raccettura */ function is_windows(){ if(PHP_OS == ‘WINNT‘ || PHP_OS == ‘WIN32‘){ return true; } return false; } ?> </body> </html>
繼續(xù)php多線程/進(jìn)程的問(wèn)題
昨天找到了進(jìn)程后臺(tái)運(yùn)行的方法,今天測(cè)試了一下,發(fā)現(xiàn)popen的速度很慢,要40-50毫秒,exec更慢!類似的程序調(diào)用命令,都要經(jīng)過(guò)系統(tǒng)調(diào)用,每次都開(kāi)啟一個(gè)php進(jìn)程想必很慢。
比較笨的辦法還是用fsockopen去通過(guò)http在server端get,試了一下,這樣不會(huì)慢,缺點(diǎn)是增加了apache負(fù)載,每個(gè)請(qǐng)求都要在后臺(tái)再請(qǐng)求一次。
我寫了段腳本test.php,用fsockopen循環(huán)連接本地另外一個(gè)腳本test1.php,不做任何操作立即關(guān)閉連接,test1.php每次在文本文件test.cache中寫入一行,循環(huán)100次的時(shí)候執(zhí)行很快,test.cache中也正確的記錄了100行。當(dāng)循環(huán)1000次的時(shí)候,問(wèn)題就來(lái)了,test.php執(zhí)行了21.6888360977秒,也就是21秒內(nèi)向apache發(fā)了1000個(gè)請(qǐng)求連test1.php,系統(tǒng)馬上沒(méi)有響應(yīng)了,內(nèi)存占用飆升到1G多,一分鐘之后才恢復(fù)正常。test.cache中丟失63行,可能是由于apache超載造成的,但是系統(tǒng)內(nèi)存卻始終沒(méi)有降下來(lái),apache的占用了83M,剩下的不知道怎么回事- -
最后又找了半天,找到了fork實(shí)現(xiàn)的真正多線程!fork是pcntl(Process Control Functions)下的一個(gè)函數(shù),pcntl只支持*nix系統(tǒng),目前沒(méi)有windows下的相關(guān)模塊。(文檔中說(shuō)需要在編譯php時(shí)--enable-pcntl,我用phpize編譯成php模塊的方式,通過(guò)在php.ini中添加extension=pcntl.so也可以使用。)
php手冊(cè)里面就有了,但是在網(wǎng)上幾乎找不到中文的文檔!文檔里面有這樣一個(gè)實(shí)例:<?php declare(ticks=1); echo "I‘m going to be a Dad.n"; if (spawn_child(‘child_function‘)) { echo ‘Parent pid:‘.posix_getpid()."n"; } echo "I‘m going to be a Dad again!.n"; if (spawn_child(‘child_function‘,1,2,3,4)) { echo ‘Child - 2 Parent pid:‘.posix_getpid()."n"; } echo "What you‘re pregnant again!?.n"; if (spawn_child(‘grand_children‘)) { echo ‘Child - 3 Parent pid:‘.posix_getpid()."n"; } function grand_children() { echo "Dad I‘m going to have a baby.n"; spawn_child(‘child_function‘,‘Joe‘); echo "I‘m so proud of my kids.n"; } function spawn_child($function) { $original_pid = posix_getpid(); $pid = pcntl_fork(); if ($pid == -1) { die ("Unable to spawn childn"); } $new_pid = posix_getpid(); if ($new_pid == $original_pid) { return true; } if (function_exists($function)) { $numargs = func_num_args(); if ($numargs > 1) { $args = func_get_args(); //print_r ($args); unset($args[0]); call_user_func($function,$args); } else { call_user_func($function); } echo "Done with child ".$new_pid." they moved out.n"; exit; } else { die ("$function does not existn"); } } function child_function() { echo ‘Child pid:‘.posix_getpid()."n"; $args = func_get_args(); if (!empty($args)) print_r ($args); } pcntl_wait( $status); ?>用pcntl實(shí)現(xiàn)的多線程可以解決很多問(wèn)題了,但是似乎還是不能解決我的問(wèn)題。在多線程雖然可以讓程序并行執(zhí)行,但所有的程序仍然在前臺(tái)完成,在所有的線程完成之前,瀏覽器仍然會(huì)顯示載入狀態(tài)。在這種狀態(tài)下,javascript代碼不會(huì)執(zhí)行,而我正是需要php產(chǎn)生一段javascript腳本,我想我的目的可能不是多線程或者并發(fā)處理,而是異步調(diào)用、后臺(tái)執(zhí)行,讓生成腳本之外的操作在后臺(tái)執(zhí)行。
所以我的這種情況用最開(kāi)始的fsockopen()方法可能更有效,但是fsockopen是不能進(jìn)行異步處理的,如果要跟打開(kāi)的連接進(jìn)行交互,就沒(méi)有任何優(yōu)勢(shì)可言了。不過(guò)我在網(wǎng)上找到了可以進(jìn)行異步通信的辦法,其實(shí)是變相的實(shí)現(xiàn)了多線程:php多路復(fù)用(多線程)[翻譯]