来自 计算机编程 2019-12-29 05:22 的文章
当前位置: 澳门威尼斯人平台 > 计算机编程 > 正文

PHP多进程编程实例说明

PHP有一组进程控制函数(编译时需要enable-pcntl与posix扩展),使得php能实现跟c一样的创建子进程、使用exec函数执行程序、处理信号等功能。

羡慕火影忍者里鸣人的影分身么?没错,PHP程序是可以开动影分身的!想完成任务,又觉得一个进程太慢,那么,试试用多进程来搞吧。这篇文章将会介绍一下PHP多进程的基本需求,如何创建多进程以及基本的信号控制,暂时不会告诉你如何进行进程间通信和信息共享。

PHP有一组进程控制函数(编译时需要 –enable-pcntl与posix扩展),使得php能在*nix系统中实现跟c一样的创建子进程、使用exec函数执行程序、处理信号等功能。 PCNTL使用ticks来作为信号处理机制(signal handle callback mechanism),可以最小程度地降低处理异步事件时的负载。何谓ticks?Tick 是一个在代码段中解释器每执行 N 条低级语句就会发生的事件,这个代码段需要通过declare来指定。

php header('content-type:text/html;charset=utf-8' ); // 必须加载扩展 if (!function_exists("pcntl_fork")) { die("pcntl extention is must !"); } //总进程的数量 $totals = 3; // 执行的脚本数量 $cmdArr = array(); // 执行的脚本数量的数组 for ($i = 0; $i  $totals; $i++) { $cmdArr[] = array("path" = __DIR__ . "/run.php", 'pid' =$i ,'total' =$totals); } /* 展开:$cmdArr Array ( [0] = Array ( [path] = /var/ [pid] = 0 [total] = 3 ) [1] = Array ( [path] = /var/ [pid] = 1 [total] = 3 ) [2] = Array ( [path] = /var/ [pid] = 2 [total] = 3 ) ) */ pcntl_signal(SIGCHLD, SIG_IGN); //如果父进程不关心子进程什么时候结束,子进程结束后,内核会回收。 foreach ($cmdArr as $cmd) { $pid = pcntl_fork(); //创建子进程 //父进程和子进程都会执行下面代码 if ($pid == -1) { //错误处理:创建子进程失败时返回-1. die('could not fork'); } else if ($pid) { //父进程会得到子进程号,所以这里是父进程执行的逻辑 //如果不需要阻塞进程,而又想得到子进程的退出状态,则可以注释掉pcntl_wait($status)语句,或写成: pcntl_wait($status,WNOHANG); //等待子进程中断,防止子进程成为僵尸进程。 } else { //子进程得到的$pid为0, 所以这里是子进程执行的逻辑。 $path = $cmd["path"]; $pid = $cmd['pid'] ; $total = $cmd['total'] ; echo exec("/usr/bin/php {$path} {$pid} {$total}")."/n"; exit(0) ; } }  

1. 准备

常用的PCNTL函数

使用PHP真正的多进程运行模式,适用于数据采集、邮件群发、数据源更新、tcp服务器等环节。

在动手之前,请确定你用的不是M$ Windows平台(因为我没有Windows)。Linux / BSD / Unix应该都是没问题的。确认好了工作环境以后一起来看看我们需要的PHP模块是否都有。打开终端输入下面的命令:

   1. pcntl_alarm ( int $seconds )

PHP有一组进程控制函数(编译时需要 enable-pcntl与posix扩展),使得php能在*nix系统中实现跟c一样的创建子进程、使用exec函数执行程序、处理信号等功能。 PCNTL使用ticks来作为信号处理机制,可以最小程度地降低处理异步事件时的负载。何谓ticks?Tick 是一个在代码段中解释器每执行 N 条低级语句就会发生的事件,这个代码段需要通过declare来指定。

复制代码 代码如下:
$ php -m

      设置一个$seconds秒后发送SIGALRM信号的计数器
   2. pcntl_signal ( int $signo , callback $handler [, bool $restart_syscalls ] )

常用的PCNTL函数1. pcntl_alarm ( int $seconds )设置一个$seconds秒后发送SIGALRM信号的计数器

这个命令检查并打印当前PHP所有开启的扩展,看一下pcntl和posix是否在输出的列表中。

      为$signo设置一个处理该信号的回调函数。下面是一个隔5秒发送一个SIGALRM信号,并由signal_handler函数获取,然后打印一个“Caught SIGALRM”的例子:
declare(ticks = 1);

2. pcntl_signal ( int $signo , callback $handler [, bool $restart_syscalls ] )为$signo设置一个处理该信号的回调函数。下面是一个隔5秒发送一个SIGALRM信号,并由signal_handler函数获取,然后打印一个“Caught SIGALRM”的例子:

1.1. pcntl

function signal_handler($signal) {
print “Caught SIGALRMn”;
pcntl_alarm(5);
}

phpdeclare(ticks = 1); function signal_handler($signal) { print "Caught SIGALRM/n"; pcntl_alarm(5);} pcntl_signal(SIGALRM, "signal_handler", true);pcntl_alarm(5); for(;;) {} 

如果找不到pcntl,八成是编译的时候没把这个扩展编译进去。如果你和我一样是编译安装的PHP,那么需要重新编译安装PHP。在配置的时候记得加上--enable-pcntl参数即可。

pcntl_signal(SIGALRM, “signal_handler”, true);
pcntl_alarm(5);

3. pcntl_exec ( string $path [, array $args [, array $envs ]] )在当前的进程空间中执行指定程序,类似于c中的exec族函数。所谓当前空间,即载入指定程序的代码覆盖掉当前进程的空间,执行完该程序进程即结束。

复制代码 代码如下:
$ cd /path/to/php_source_code_dir
$ ./configure [some other options] --enable-pcntl
$ make && make install

for(;;) {
}

php$dir = '/home/shankka/';$cmd = 'ls';$option = '-l';$pathtobin = '/bin/ls'; $arg = array($cmd, $option, $dir); pcntl_exec($pathtobin, $arg);echo '123'; //不会执行到该行

1.2. posix

?>

4. pcntl_fork ( void )为当前进程创建一个子进程,并且先运行父进程,返回的是子进程的PID,肯定大于零。在父进程的代码中可以用 pcntl_wait暂停父进程知道他的子进程有返回值。注意:父进程的阻塞同时会阻塞子进程。但是父进程的结束不影响子进程的运行。父进程运行完了会接着运行子进程,这时子进程会从执行pcntl_fork的那条语句开始执行,但是此时它返回的是零。在子进程的代码块中最好有exit语句,即执行完子进程后立即就结束。否则它会又重头开始执行这个脚本的某些部分。

这货一般默认就会装上,只要你编译的时候没有加上--disable-posix。

   3. pcntl_exec ( string $path [, array $args [, array $envs ]] )

注意两点:

2. 预备知识

      在当前的进程空间中执行指定程序,类似于c中的exec族函数。所谓当前空间,即载入指定程序的代码覆盖掉当前进程的空间,执行完该程序进程即结束。
$dir = '/home/shankka/';
$cmd = 'ls';
$option = '-l';
$pathtobin = '/bin/ls';

子进程最好有一个exit;语句,防止不必要的出错; pcntl_fork间最好不要有其它语句,例如:

在继续之前,你还需要对Linux多进程有一点了解。多进程是咋回事呢?这里可跟火影忍者里的影分身稍微有点不同。首先,鸣人从小长到大,比如16岁,咳。有一天他发动了影分身,分出了5个他。显然,这些分身也是16岁的鸣人而不是刚出生啥也不懂就会哭的婴儿(那叫克隆)。然后,不一样的地方来了:分身们变成了独立的人各自去做各自的事,互相之间不再知道其他分身和原身都做了什么(当然不会像动画片里一样积累经验给原身啦)。除非,他们互相之间有交流,不然,只有16岁之前的事情才是他们共同的记忆。

$arg = array($cmd, $option, $dir);

php$pid = pcntl_fork();//这里最好不要有其他的语句if ($pid == -1) { die('could not fork');} else if ($pid) { // we are the parentpcntl_wait($status); //Protect against Zombie children} else { // we are the child}

有同学说了,老大你这不坑爹呢么?我又没看过火影忍者!那你去看一遍好了……

pcntl_exec($pathtobin, $arg);
echo '123'; //不会执行到该行
?>

5. pcntl_wait ( int &$status [, int $options ] )阻塞当前进程,只到当前进程的一个子进程退出或者收到一个结束当前进程的信号。使用$status返回子进程的状态码,并可以指定第二个参数来说明是否以阻塞状态调用:阻塞方式调用的,函数返回值为子进程的pid,如果没有子进程返回值为-1;非阻塞方式调用,函数还可以在有子进程在运行但没有结束的子进程时返回0。

最后,预备知识完了,就是大致了解一下主进程开出来的子进程是怎么回事。子进程的代码和主进程是完全一样的,还有一部分一样的东西就是直到发动影分身之前执行的所有内容。

   4. pcntl_fork ( void )

6. pcntl_waitpid ( int $pid , int &$status [, int $options ] )功能同pcntl_wait,区别为waitpid为等待指定pid的子进程。当pid为-1时pcntl_waitpid与pcntl_wait 一样。在pcntl_wait和pcntl_waitpid两个函数中的$status中存了子进程的状态信息,这个参数可以用于 pcntl_wifexited、pcntl_wifstopped、pcntl_wifsignaled、pcntl_wexitstatus、 pcntl_wtermsig、pcntl_wstopsig、pcntl_waitpid这些函数。例如:

3. 影分身之术

      为当前进程创建一个子进程,并且先运行父进程,返回的是子进程的PID,肯定大于零。在父进程的代码中可以用 pcntl_wait(&$status)暂停父进程知道他的子进程有返回值。注意:父进程的阻塞同时会阻塞子进程。但是父进程的结束不影响子进程的运行。

php$pid = pcntl_fork();if($pid) { pcntl_wait($status); $id = getmypid(); echo "parent process,pid {$id}, child pid {$pid}/n";}else{ $id = getmypid(); echo "child process,pid {$id}/n"; sleep(2);}

所以呢,没有点基础知识怎么能理解卷轴里的内容呢?打开卷轴首先看到了一个单词:fork。

      父进程运行完了会接着运行子进程,这时子进程会从执行pcntl_fork()的那条语句开始执行(包括此函数),但是此时它返回的是零(代表这是一个子进程)。在子进程的代码块中最好有exit语句,即执行完子进程后立即就结束。否则它会又重头开始执行这个脚本的某些部分。

子进程在输出child process等字样之后sleep了2秒才结束,而父进程阻塞着直到子进程退出之后才继续运行。

3.1. fork

      注意两点:
      1. 子进程最好有一个exit;语句,防止不必要的出错;
      2. pcntl_fork间最好不要有其它语句,例如:
$pid = pcntl_fork();
//这里最好不要有其他的语句
if ($pid == -1) {
die('could not fork');
} else if ($pid) {
// we are the parent
pcntl_wait($status); //Protect against Zombie children
} else {
// we are the child
}

7. pcntl_getpriority ([ int $pid [, int $process_identifier ]] )取得进程的优先级,即nice值,默认为0,在我的测试环境的linux中,优先级为-20到19,-20为优先级最高,19为最低。。

叉子?叉子是分岔的,一个变多个嘛!差不多就是这个意思。创建子进程就用这个命令。这里需要用到pcntl_fork()函数。(可以先简单看一下PHP手册关于这个函数的介绍。)创建一个PHP脚本:
复制代码 代码如下:
$pid = pcntl_fork(); // 一旦调用成功,事情就变得有些不同了
if ($pid == -1) {
    die('fork failed');
} else if ($pid == 0) {
} else {
}

   5. pcntl_wait ( int &$status [, int $options ] )

8. pcntl_setpriority ( int $priority [, int $pid [, int $process_identifier ]] )设置进程的优先级。

pcntl_fork()函数创建一个子进程,子进程和父进程唯一的区别就是PID(进程ID)和PPID(父进程ID)不同。在终端下查看进程用ps命令(问问man看ps怎么用:man ps)。当函数返回值为-1的时候,说明fork失败了。试试在if前面加一句:echo $pid . PHP_EOL;。运行你的脚本,输出可能像下面这样(结果说明子进程和父进程的代码是相同的):
复制代码 代码如下:
67789 # 这个是父进程打印的
0     # 这个是子进程打印的

      阻塞当前进程,只到当前进程的一个子进程退出或者收到一个结束当前进程的信号。使用$status返回子进程的状态码,并可以指定第二个参数来说明是否以阻塞状态调用:
      1. 阻塞方式调用的,函数返回值为子进程的pid,如果没有子进程返回值为-1;
      2. 非阻塞方式调用,函数还可以在有子进程在运行但没有结束的子进程时返回0。
   6. pcntl_waitpid ( int $pid , int &$status [, int $options ] )

9. posix_kill可以给进程发送信号

pcntl_fork()函数调用成功后,在父进程中会返回子进程的PID,而在子进程中返回的是0。所以,下面直接用if分支来控制父进程和子进程做不同的事。

      功能同pcntl_wait,区别为waitpid为等待指定pid的子进程。当pid为-1时pcntl_waitpid与pcntl_wait 一样。在pcntl_wait和pcntl_waitpid两个函数中的$status中存了子进程的状态信息,这个参数可以用于 pcntl_wifexited、pcntl_wifstopped、pcntl_wifsignaled、pcntl_wexitstatus、 pcntl_wtermsig、pcntl_wstopsig、pcntl_waitpid这些函数。

10. pcntl_singal用来设置信号的回调函数

3.2. 分配任务

      例如:

当父进程退出时,子进程如何得知父进程的退出当父进程退出时,子进程一般可以通过下面这两个比较简单的方法得知父进程已经退出这个消息:

然后我们来说说鸣人16岁那次影分身的事儿,给原身和分身分配两个简单的输出任务:
复制代码 代码如下:
$parentPid = getmypid(); // 这就是传说中16岁之前的记忆
$pid = pcntl_fork(); // 一旦调用成功,事情就变得有些不同了
if ($pid == -1) {
    die('fork failed');
} else if ($pid == 0) {
    $mypid = getmypid(); // 用getmypid()函数获取当前进程的PID
    echo 'I am child process. My PID is ' . $mypid . ' and my father's PID is ' . $parentPid . PHP_EOL;
} else {
    echo 'Oh my god! I am a father now! My child's PID is ' . $pid . ' and mine is ' . $parentPid . PHP_EOL;
}

       $pid = pcntl_fork();
if($pid) {
澳门威尼斯人平台,pcntl_wait($status);
$id = getmypid();
echo “parent process,pid {$id}, child pid {$pid}n”;
}else{
$id = getmypid();
echo “child process,pid {$id}n”;
sleep(2);
}
?>

当父进程退出时,会有一个INIT进程来领养这个子进程。这个INIT进程的进程号为1,所以子进程可以通过使用getppid()来取得当前父进程的pid。如果返回的是1,表明父进程已经变为INIT进程,则原进程已经推出。使用kill函数,向原有的父进程发送空信号。使用这个方法对某个进程的存在性进行检查,而不会真的发送信号。所以,如果这个函数返回-1表示父进程已经退出。

输出的结果可能是这样:
复制代码 代码如下:
Oh my god! I am a father now! My child's PID is 68066 and mine is 68065
I am child process. My PID is 68066 and my father's PID is 68065

      子进程在输出child process等字样之后sleep了2秒才结束,而父进程阻塞着直到子进程退出之后才继续运行。
   7. pcntl_getpriority ([ int $pid [, int $process_identifier ]] )

本文由澳门威尼斯人平台发布于计算机编程,转载请注明出处:PHP多进程编程实例说明

关键词: