熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> PHP編程 >> 正文

深入解析PHP中的(偽)多線程與多進程

2013-11-15 12:30:34  來源: PHP編程 
本篇文章是對PHP中的(偽)多線程與多進程進行了詳細的分析介紹需要的朋友參考下  

  (偽)多線程借助外力
利用WEB服務器本身的多線程來處理從WEB服務器多次調用我們需要實現多線程的程序
QUOTE:
我們知道PHP本身是不支持多線程的 但是我們的WEB服務器是支持多線程的
也就是說可以同時讓多人一起訪問 這也是我在PHP中實現多線程的基礎
假設我們現在運行的是aphp這個文件 但是我在程序中又請求WEB服務器運行另一個bphp
那麼這兩個文件將是同時執行的
(PS: 一個鏈接請求發送之後 WEB服務器就會執行它 而不管客戶端是否已經退出)
有些時候 我們想運行的不是另一個文件 而是本文件中的一部分代碼該怎麼辦呢?
其實可是通過參數來控制aphp來運行哪一段程序
下面看一個例子:

復制代碼 代碼如下:
<?php
function runThread(){
$fp = fsockopen(localhost $errno $errmsg);
fputs($fp "GET /aphp?act=brnrn");//這裡的第二個參數是HTTP協議中規定的請求頭不明白的請看RFC中的定義
fclose($fp);
}
function a(){
$fp = fopen(result_alog w);
fputs($fp Set in Date(h:i:s time()) (double)microtime() "rn");
fclose($fp);
}
function b(){
$fp = fopen(result_blog w);
fputs($fp Set in Date(h:i:s time()) (double)microtime() "rn");
fclose($fp);
}
if(!isset($_GET[act])){ $_GET[act] = a;};
if($_GET[act] == a){
runThread();
a();
}else if($_GET[act] == b){
b();
};
?>

  
打開result_alog 和 result_blog 比較一下兩個文件的中訪問的時間 大家會發現 這兩個的確是在不同線程中運行的有些時間完全一樣
上面只是一個簡單的例子 大家可以改進成其它形式
既然PHP中也能多線程了 那麼問題也來了 那就是同步的問題 我們知道 PHP本身是不支持多線程的 所以更不會有什麼像Java 中synchronize的方法了 那我們該如何做呢

   盡量不訪問同一個資源 以避免沖突 但是可以同時像數據庫操作 因為數據庫是支持並發操作的 所以在多線程的PHP中
不 要向同一個文件中寫入數據 如果必須要寫的話 用別的方法進行同步 如調用 flock對文件進行加鎖等 或建立臨時文件並在另外的線程中等待這個文件的消失 while(file_exits(xxx)); 這樣就等於這個臨時文件存在時 表示其實線程正在操作如果沒有了這個文件 說明其它線程已經釋放了這個

   盡量不要從runThread在執行fputs後取這個socket中讀取數據 因為要實現多線程 需要的用非阻塞模式 即在像fgets這樣的函數時立即返回 所以讀寫數據就會出問題 如果使用阻塞模式的話 程序就不算是多線程了 他要等上面的返回才執行下面的程序 所以如果需要交換數據最後利用外面文件或數據中完成 實在想要的話就用socket_set_nonblock($fp) 來實現

說了這麼多 倒底這個有沒有實際的意義呢? 在什麼時候需要這種用這種方法呢 ?
答案是肯定的 大家知道 在一個不斷讀取網絡資源的應用中 網絡的速度是瓶頸 如果采多這種形式就可以同時以多個線程對不同的頁面進行讀取

本人做的一個能從soaso這些商城網站搜索信息的程序還有一個從阿裡巴巴網站上讀取商業信息和公司目錄的程序也用到了此技術 因為這兩個程序都是要不斷的鏈接它們的服務器讀取信息並保存到數據庫 利用此技術正好消除了在等待響應時的瓶頸

  多進程使用PHP的Process Control Functions(PCNTL/線程控制函數)
只能用在Unix Like OSWindows不可用
編譯php的時候需要加上enablepcntl且推薦僅僅在CLI模式運行不要在WEB服務器環境運行
以下為簡短的測試代碼

復制代碼 代碼如下:
declare(ticks=);
$bWaitFlag = FALSE; /// 是否等待進程結束
$intNum = ; /// 進程總數
$pids = array(); /// 進程PID數組
echo ("Startn");
for($i = ; $i < $intNum; $i++) {
$pids[$i] = pcntl_fork();/// 產生子進程而且從當前行之下開試運行代碼而且不繼承父進程的數據信息
if(!$pids[$i]) {
// 子進程進程代碼段_Start
$str="";
sleep(+$i);
for ($j=;$j<$i;$j++) {$str="*";}
echo "$i > " time() " $str n";
exit();
// 子進程進程代碼段_End
}
}
if ($bWaitFlag)
{
for($i = ; $i < $intNum; $i++) {
pcntl_waitpid($pids[$i] $status WUNTRACED);
echo "wait $i > " time() "n";
}
}
echo ("Endn");

  
運行結果如下
CODE:[Copy toclipboard][qiao@oicq qiao]$ phptestphp
Start
End
[qiao@oicq qiao]$ ps aux | grep "php"
qiao pts/ S : :/usr/local/php/b
qiao pts/ S : :/usr/local/php/b
qiao pts/ S : :/usr/local/php/b
qiao pts/ S : :/usr/local/php/b
qiao pts/ S : :/usr/local/php/b
qiao pts/ S : : /usr/local/php/b
qiao pts/ S : :/usr/local/php/b
qiao pts/ S : :/usr/local/php/b
qiao pts/ S : :/usr/local/php/b
qiao pts/ S : :/usr/local/php/b
qiao pts/ S : : grep php
[qiao@oicq qiao]$ >
> *
> **
> ***
> ****
> *****
> ******
> *******
> ********
> *********
[qiao@oicq qiao]$
如果$bWaitFlag=TURE則結果如下
CODE:[Copy toclipboard][qiao@oicq qiao]$ phptestphp
Start
>
wait >
> *
wait >
> **
wait >
> ***
wait >
> ****
wait >
> *****
wait >
> ******
wait >
> *******
wait >
> ********
wait >
> *********
wait >
End
[qiao@oicq qiao]$
從 多進程的例子可以看出使用pcntl_fork()之後將生成一個子進程而且子進程運行的代碼從pcntl_fork()之後的代碼開始而子進 程不繼承父進程的數據信息(實際上是把父進程的數據做了一個全新的拷貝)因而使用if(!$pids[$i]) 來控制子進程實際運行的代碼段
更詳細的研究出於時間關系暫時沒有進行你可以參考我給出的手冊的鏈接

  [文章二] 嘗試php命令行腳本多進程並發執行
除了fork cli下的並發方式還有一種看我的例子:
php不支持多線程但是我們可以把問題轉換成“多進程”來解決由於php中的pcntl_fork只有unix平台才可以使用所以本文嘗試使用popen來替代
下面是一個例子
被並行調用的子程序代碼

復制代碼 代碼如下:
<?php
if($argc==){
echo("argvn");
}
$arg = $argv[];
for($i=; $i<; $i++)
{
echo($i""time()" exec $arg n");
if($arg==php){
sleep();
echo($i""time()" exec $arg n");
sleep();
}else{
sleep();
}
}
?>

  
主調用者程序由他調用子進程同時並發的收集子程序的輸出

復制代碼 代碼如下:
error_reporting(E_ALL);
$handle = popen(php subphp php r);
$handle = popen(php subphp php r);
$handle = popen(php subphp php r);
echo "$handle; " gettype($handle) "n";
echo "$handle; " gettype($handle) "n";
echo "$handle; " gettype($handle) "n";
//sleep();
while(!feof($handle) || !feof($handle) || !feof($handle) )
{
$read = fgets($handle);
echo $read;
$read = fgets($handle);
echo $read;
$read = fgets($handle);
echo $read;
}
pclose($handle);
pclose($handle);
pclose($handle);

  
下面是我機器上的輸出
C:my_hunter>php execphp
Resource id #; resource
Resource id #; resource
Resource id #; resource
exec php
exec php
exec php
exec php
exec php
exec php
exec php
exec php
exec php
exec php
exec php
exec php
exec php
exec php
exec php
exec php
exec php
exec php
exec php
exec php
exec php
exec php
exec php
exec php
exec php
exec php
exec php
exec php
exec php
exec php
exec php
exec php
exec php
exec php
exec php
exec php
exec php
exec php
exec php
exec php
**總結**
**主程序循環等待子進程 通過fgets或fread 把子進程的輸出獲取出來 從時間戳上看的確實現了並發執行**

以後的改進
* popen打開的句柄是單向的如果需要向子進程交互可以使用proc_open
* 使用數組和子函數代替while(!feof($handle)|| !feof($handle) || !feof($handle) )這種龌龊的寫法
* 用fread一次把子進程已經產生的輸出取完而不是每次一行
一個並發執行shell任務的調度者本程序讀取一個任務文件把裡面的每行命令並發執行 可以設置同時存在的子進程數目:

復制代碼 代碼如下:
/*
主任務管理器
並發的執行子任務列表
*/
include("/common/confphp");
include("/common/functionphp");
//開啟的進程數
$exec_number = ;
/***** main ********/
if($argc==){
echo("argvn");
}
$taskfile = $argv[];
//tasklist
$tasklist = file($taskfile);
$tasklist_len = count($tasklist);
$tasklist_pos = ;
$handle_list = array();
while()
{
//子進程列表有空閒則填充補齊子進程列表
if($exec_number > count($handle_list) &&
$tasklist_pos < $tasklist_len)
{
for($i=$tasklist_pos; $i<$tasklist_len; )
{
$command = $tasklist[$i] ;
$handle_list[] = popen($command "r" );
tolog("begin task t "$tasklist[$i]);
$i++;
if($exec_number == count($handle_list)) break;
}
$tasklist_pos = $i;
}
//如果子進程列表空退出
if( == count($handle_list))
{
break;
}
//檢查子進程列表的輸出把停掉的子進程關閉並記錄下來
$end_handle_keys = array();
foreach($handle_list as $key => $handle)
{
//$str = fgets($handle );
$str = fread($handle );
echo($str);
if(feof($handle))
{
$end_handle_keys[] = $key;
pclose($handle);
}
}
//踢出停掉的子進程
foreach($end_handle_keys as $key)
{
unset($handle_list[$key]);
//var_dump($handle_list);
//exit;
}
}
tolog("nn*******************end**********************nn" "" true);

  
附加一段Socket多進程接收的代碼

復制代碼 代碼如下:

  
do {
if (($msgsock = socket_accept($sock)) < ) {
echo "socket_accept() failed: reason: " socket_strerror($msgsock) "n";
break;
}
$pid = pcntl_fork();
if ($pid == ) {
die(could not fork);
} else if (!$pid) {

socket_write($msgsock $msg strlen($msg));
do {

} while (true);
socket_close($msgsock);
}
} while (true);


From:http://tw.wingwit.com/Article/program/PHP/201311/21087.html
    Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.