一、引言
進(jìn)程是一個(gè)具有獨(dú)立功能的程序關(guān)于某個(gè)數(shù)據(jù)集合的一次運(yùn)行活動(dòng)。換句話說(shuō)就是,在系統(tǒng)調(diào)度多個(gè)cpu的時(shí)候,一個(gè)程序的基本單元。進(jìn)程對(duì)于大多數(shù)的語(yǔ)言都不是一個(gè)陌生的概念,作為"世界上最好的語(yǔ)言PHP"當(dāng)然也例外。
二、環(huán)境
php中的進(jìn)程是以擴(kuò)展的形式來(lái)完成。通過(guò)這些擴(kuò)展,我們能夠很輕松的完成進(jìn)程的一系列動(dòng)作。
- pcntl擴(kuò)展:主要的進(jìn)程擴(kuò)展,完成進(jìn)程創(chuàng)建于等待操作。
- posix擴(kuò)展:完成posix兼容機(jī)通用api,如獲取進(jìn)程id,殺死進(jìn)程等。
- sysvmsg擴(kuò)展:實(shí)現(xiàn)system v方式的進(jìn)程間通信之消息隊(duì)列。
- sysvsem擴(kuò)展:實(shí)現(xiàn)system v方式的信號(hào)量。
- sysvshm擴(kuò)展:實(shí)現(xiàn)system v方式的共享內(nèi)存。
- sockets擴(kuò)展:實(shí)現(xiàn)socket通信。
這些擴(kuò)展只能在linux/mac中使用,window下是不支持。最后建議php版本為5.5+。
相關(guān)代碼:進(jìn)程相關(guān)代碼
三、簡(jiǎn)單的例子
一個(gè)簡(jiǎn)單的PHP多進(jìn)程例子,該例子中,一個(gè)子進(jìn)程,一個(gè)父進(jìn)程。子進(jìn)程輸出5次,退出程序。
$parentPid = posix_getpid();
echo "parent progress pid:{$parentPid}\n";
$childList = array();
$pid = pcntl_fork();
if ( $pid == -1) {
// 創(chuàng)建失敗
exit("fork progress error!\n");
} else if ($pid == 0) {
// 子進(jìn)程執(zhí)行程序
$pid = posix_getpid();
$repeatNum = 5;
for ( $i = 1; $i <= $repeatNum; $i++) {
echo "({$pid})child progress is running! {$i} \n";
$rand = rand(1,3);
sleep($rand);
}
exit("({$pid})child progress end!\n");
} else {
// 父進(jìn)程執(zhí)行程序
$childList[$pid] = 1;
}
// 等待子進(jìn)程結(jié)束
pcntl_wait($status);
echo "({$parentPid})main progress end!";
完美,終于創(chuàng)建了一個(gè)子進(jìn)程,一個(gè)父進(jìn)程。完了么?沒(méi)有,各個(gè)進(jìn)程之間相互獨(dú)立的,沒(méi)有任何交集,使用范圍嚴(yán)重受到現(xiàn)在。怎么辦,哪就進(jìn)程間通信(interprogress communication)唄。
四、進(jìn)程間通信(IPC)
通常linux中的進(jìn)程通信方式有:消息隊(duì)列、信號(hào)量、共享內(nèi)存、信號(hào)、管道、socket。
1.消息隊(duì)列
消息隊(duì)列是存放在內(nèi)存中的一個(gè)隊(duì)列。如下代碼將創(chuàng)建3個(gè)生產(chǎn)者子進(jìn)程,2個(gè)消費(fèi)者子進(jìn)程。這5個(gè)進(jìn)程將通過(guò)消息隊(duì)列通信。
$parentPid = posix_getpid();
echo "parent progress pid:{$parentPid}\n";$childList = array();
// 創(chuàng)建消息隊(duì)列,以及定義消息類型(類似于數(shù)據(jù)庫(kù)中的庫(kù))
$id = ftok(__FILE__,'m');
$msgQueue = msg_get_queue($id);
const MSG_TYPE = 1;
// 生產(chǎn)者
function producer(){
global $msgQueue;
$pid = posix_getpid();
$repeatNum = 5;
for ( $i = 1; $i <= $repeatNum; $i++) {
$str = "({$pid})progress create! {$i}";
msg_send($msgQueue,MSG_TYPE,$str);
$rand = rand(1,3);
sleep($rand);
}
}
// 消費(fèi)者
function consumer(){
global $msgQueue;
$pid = posix_getpid();
$repeatNum = 6;
for ( $i = 1; $i <= $repeatNum; $i++) {
$rel = msg_receive($msgQueue,MSG_TYPE,$msgType,1024,$message);
echo "{$message} | consumer({$pid}) destroy \n";
$rand = rand(1,3);
sleep($rand);
}
}
function createProgress($callback){
$pid = pcntl_fork();
if ( $pid == -1) {
// 創(chuàng)建失敗
exit("fork progress error!\n");
} else if ($pid == 0) {
// 子進(jìn)程執(zhí)行程序
$pid = posix_getpid();
$callback();
exit("({$pid})child progress end!\n");
}else{
// 父進(jìn)程執(zhí)行程序
return $pid;
}
}
// 3個(gè)寫(xiě)進(jìn)程
for ($i = 0; $i < 3; $i ++ ) {
$pid = createProgress('producer');
$childList[$pid] = 1;
echo "create producer child progress: {$pid} \n";
}
// 2個(gè)寫(xiě)進(jìn)程
for ($i = 0; $i < 2; $i ++ ) {
$pid = createProgress('consumer');
$childList[$pid] = 1;
echo "create consumer child progress: {$pid} \n";
}
// 等待所有子進(jìn)程結(jié)束
while(!empty($childList)){
$childPid = pcntl_wait($status);
if ($childPid > 0){
unset($childList[$childPid]);
}
}
echo "({$parentPid})main progress end!\n";
由于消息隊(duì)列去數(shù)據(jù)是,只有一個(gè)進(jìn)程能去到,所以不需要額外的鎖或信號(hào)量。
2. 信號(hào)量與共享內(nèi)存
信號(hào)量:是系統(tǒng)提供的一種原子操作,一個(gè)信號(hào)量,同時(shí)只有你個(gè)進(jìn)程能操作。一個(gè)進(jìn)程獲得了某個(gè)信號(hào)量,就必須被該進(jìn)程釋放掉。
共享內(nèi)存:是系統(tǒng)在內(nèi)存中開(kāi)辟的一塊公共的內(nèi)存區(qū)域,任何一個(gè)進(jìn)程都可以訪問(wèn),在同一時(shí)刻,可以有多個(gè)進(jìn)程訪問(wèn)該區(qū)域,為了保證數(shù)據(jù)的一致性,需要對(duì)該內(nèi)存區(qū)域加鎖或信號(hào)量。
以下,創(chuàng)建多個(gè)進(jìn)程修改內(nèi)存中的同一個(gè)值。
$parentPid = posix_getpid();
echo "parent progress pid:{$parentPid}\n";
$childList = array();
// 創(chuàng)建共享內(nèi)存,創(chuàng)建信號(hào)量,定義共享key
$shm_id = ftok(__FILE__,'m');
$sem_id = ftok(__FILE__,'s');
$shareMemory = shm_attach($shm_id);
$signal = sem_get($sem_id);
const SHARE_KEY = 1;
// 生產(chǎn)者
function producer(){
global $shareMemory;
global $signal;
$pid = posix_getpid();
$repeatNum = 5;
for ( $i = 1; $i <= $repeatNum; $i++) {
// 獲得信號(hào)量
sem_acquire($signal);
if (shm_has_var($shareMemory,SHARE_KEY)){
// 有值,加一
$count = shm_get_var($shareMemory,SHARE_KEY);
$count ++;
shm_put_var($shareMemory,SHARE_KEY,$count);
echo "({$pid}) count: {$count}\n";
}else{
// 無(wú)值,初始化
shm_put_var($shareMemory,SHARE_KEY,0);
echo "({$pid}) count: 0\n";
}
// 用完釋放
sem_release($signal);
$rand = rand(1,3);
sleep($rand);
}
}
function createProgress($callback){
$pid = pcntl_fork();
if ( $pid == -1) {
// 創(chuàng)建失敗
exit("fork progress error!\n");
} else if ($pid == 0) {
// 子進(jìn)程執(zhí)行程序
$pid = posix_getpid();
$callback();
exit("({$pid})child progress end!\n");
}else{
// 父進(jìn)程執(zhí)行程序
return $pid;
}
}
// 3個(gè)寫(xiě)進(jìn)程
for ($i = 0; $i < 3; $i ++ ) {
$pid = createProgress('producer');
$childList[$pid] = 1;
echo "create producer child progress: {$pid} \n";
}
// 等待所有子進(jìn)程結(jié)束
while(!empty($childList)){
$childPid = pcntl_wait($status);
if ($childPid > 0){
unset($childList[$childPid]);
}
}
// 釋放共享內(nèi)存與信號(hào)量
shm_remove($shareMemory);
sem_remove($signal);
echo "({$parentPid})main progress end!\n";
3.信號(hào)
信號(hào)是一種系統(tǒng)調(diào)用。通常我們用的kill命令就是發(fā)送某個(gè)信號(hào)給某個(gè)進(jìn)程的。具體有哪些信號(hào)可以在liunx/mac中運(yùn)行kill -l查看。下面這個(gè)例子中,父進(jìn)程等待5秒鐘,向子進(jìn)程發(fā)送sigint信號(hào)。子進(jìn)程捕獲信號(hào),掉信號(hào)處理函數(shù)處理。
$parentPid = posix_getpid();
echo "parent progress pid:{$parentPid}\n";
// 定義一個(gè)信號(hào)處理函數(shù)
function sighandler($signo) {
$pid = posix_getpid();
echo "{$pid} progress,oh no ,I'm killed!\n";
exit(1);
}
$pid = pcntl_fork();
if ( $pid == -1) {
// 創(chuàng)建失敗
exit("fork progress error!\n");
} else if ($pid == 0) {
// 子進(jìn)程執(zhí)行程序
// 注冊(cè)信號(hào)處理函數(shù)
declare(ticks=10);
pcntl_signal(SIGINT, "sighandler");
$pid = posix_getpid();
while(true){
echo "{$pid} child progress is running!\n";
sleep(1);
}
exit("({$pid})child progress end!\n");
}else{
// 父進(jìn)程執(zhí)行程序
$childList[$pid] = 1;
// 5秒后,父進(jìn)程向子進(jìn)程發(fā)送sigint信號(hào).
sleep(5);
posix_kill($pid,SIGINT);
sleep(5);
}
echo "({$parentPid})main progress end!\n";
4.管道(有名管道)
管道是比較常用的多進(jìn)程通信手段,管道分為無(wú)名管道與有名管道,無(wú)名管道只能用于具有親緣關(guān)系的進(jìn)程間通信,而有名管道可以用于同一主機(jī)上任意進(jìn)程。這里只介紹有名管道。下面的例子,子進(jìn)程寫(xiě)入數(shù)據(jù),父進(jìn)程讀取數(shù)據(jù)。
// 定義管道路徑,與創(chuàng)建管道
$pipe_path = '/data/test.pipe';
if(!file_exists($pipe_path)){
if(!posix_mkfifo($pipe_path,0664)){
exit("create pipe error!");
}
}
$pid = pcntl_fork();
if($pid == 0){
// 子進(jìn)程,向管道寫(xiě)數(shù)據(jù)
$file = fopen($pipe_path,'w');
while (true){
fwrite($file,'hello world');
$rand = rand(1,3);
sleep($rand);
}
exit('child end!');
}else{
// 父進(jìn)程,從管道讀數(shù)據(jù)
$file = fopen($pipe_path,'r');
while (true){
$rel = fread($file,20);
echo "{$rel}\n";
$rand = rand(1,2);
sleep($rand);
}
}
5.socket
socket即我們常說(shuō)的套接字編程。這個(gè)待補(bǔ)充。