我们都知道PHP是单进程执行的,PHP处理多并发主要是依赖服务器或PHP-FPM的多进程及它【tā】们进程【chéng】的复用,但PHP实现多【duō】进程【chéng】也【yě】意义重大,尤其是【shì】在后台Cli模式下处理大量数据或运【yùn】行后【hòu】台DEMON守护进程时【shí】,多进程的优【yōu】势 : 一个任务被分解成多个进程执行,就会减少整体的耗时。
1.多开几个进程,这【zhè】种方式简单实用【yòng】,推荐【jiàn】,比如【rú】说使用shell脚【jiǎo】本:
1 2 3 4 5 6 7 8 |
#!/bin/bash
for ((i=1;i<=8;i++))
do
/usr/bin/php multiprocessTest.php &
done
wait
|
2.php多进程
php多进【jìn】程需要pcntl,可以通【tōng】过 php -m 查看【kàn】,而且多进程实现只能在cli模式下。
注:php多进程一般应用在【zài】php_cli命令行【háng】中【zhōng】执行php脚本,做进程任【rèn】务【wù】时要检查【chá】php是【shì】否【fǒu】开启了pcntl扩展,(pcntl是【shì】process control进程管理的【de】缩写【xiě】。PHP的进程控制支持实现了Unix方【fāng】式的进程创建, 程序执行, 信号处理以及【jí】进程的中断(此扩展在 Windows 平台上不可用)。该扩展在PHP中进程控制支持默认是关闭的。
1)、安装pcntl扩展
a)、未安装php7时添pcntl扩展
在配置编译中增加代码 --enable-pcntl :
1 |
. /configure -- enable -pcntl
|
b)、已安装php7添加pcntl扩展
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
###1、进入php源码包里 扩展库目录
[root@localhost ~] # cd /usr/local/src/php-7.1.22
[root@localhost php-7.1.22] # cd ext/pcntl/
###2、用phpize生成配置文件
[root@localhost pcntl] # /usr/local/php/bin/phpize
###3、指定配置文件进行配置
[root@localhost pcntl] #./configure --enable-pcntl --with-php-config=/usr/local/php/bin/php-config
###4、编译安装
[root@localhost pcntl] # make && make install
.....
Installing shared extensions: /usr/local/php/lib/php/extensions/no-debug-non-zts-20170718/
[root@localhost pcntl] #
###5、切换到 extensions 目录:
[root@localhost ~] # cd /usr/local/php/lib/php/extensions/no-debug-non-zts-20170718/
[root@localhost no-debug-non-zts-20170718] # ls
opcache.a opcache.so pcntl.so
###6、查看 php.ini 实际位置,编辑 php.ini文件【jiàn】
[root@localhost etc] # vim php.ini
添加:
extension= /usr/local/php/lib/php/extensions/no-debug-non-zts-20170718/pcntl .so
###7、添加后【hòu】 重【chóng】新【xīn】启动 php-fpm.service 生效
[root@localhost ~] # systemctl restart php-fpm.service
###8、查看 phpinfo() 信息。
|
2)、创建子进程
创建PHP子进程是多进程的开【kāi】始【shǐ】,我们需要【yào】 pcntl_fork() 函数;
pcntl_fork() — 在当前进程当前【qián】位置产生【shēng】分支(子【zǐ】进程)。此函数【shù】创建了一【yī】个新【xīn】的子进程后,子进程会继承父进程当前【qián】的上下文,和父进【jìn】程一样【yàng】从pcntl_fork()函数处继续向下执行,只是获取到【dào】的pcntl_fork()的返回【huí】值不【bú】同,我们便能从判断返【fǎn】回值来区分【fèn】父进程和子进【jìn】程,分配【pèi】父进程和子进程去做不同【tóng】的【de】逻【luó】辑处【chù】理【lǐ】。
pcntl_fork()函数成功【gōng】执行时会在父【fù】进程返回【huí】子进程的【de】进程id(pid)。fork是创建了【le】一个子进【jìn】程【chéng】,父【fù】进程和子进程 都从fork的【de】位【wèi】置开始向下【xià】继续执行,不同的是父进【jìn】程执行过程中,得到的fork返【fǎn】回值为子进程 号,而子进程得到的是0。
而pcntl_fork()函数在执行【háng】失败时【shí】,会在父进程【chéng】返回-1,当【dāng】然也不会【huì】有子进程产生。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<?php
$pid = pcntl_fork();
// 父进程和子进程都会执行下面代码
if ($pid == -1) {
// 错误处理:创建子进程失败时返回-1.
die( 'could not fork' );
} else if ($pid) {
// 父进程会得到子进程号,所以这里是父进程执行的逻辑
pcntl_wait($status); // 等待子进程中断,防止子进程成为僵尸进程。
} else {
// 子进程得到的$pid为0, 所以这里是子进程执行的逻辑。
}
|
1 2 3 4 5 |
getmypid(); // 返回当前 PHP 进程 ID,或在错误时返回 FALSE。
pcntl_fork(); // 成功【gōng】时,在父进程【chéng】执行线程内返【fǎn】回【huí】产【chǎn】生的子【zǐ】进程的PID,在子进程【chéng】执行线程内返回0。
// 失【shī】败【bài】时,在 父进程上下文【wén】返回【huí】-1,不会创建子进程,并且【qiě】会引发一个PHP错【cuò】误。
posix_getpid(); // 获取当前进程的pid;
|
以下是fork子进程的一个简单的小例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<?php
foreach (range(1, 5) as $index) {
$pid = pcntl_fork();
if ($pid === -1) {
echo "failed to fork!n" ;
exit ;
} elseif ($pid) {
pcntl_wait($status); // 父进程必须等待一个子进程退出后,再创建下一个子进程。
echo "I am the parent, pid: $pidn" ;
} else {
$cid = posix_getpid();
echo "fork the {$index}th child, pid: $cidn" ;
exit ; // 必须
}
}
|
这个【gè】例子非常简单,循环创【chuàng】建5个进程,在各【gè】个进程【chéng】里【lǐ】面打【dǎ】印一句【jù】话,主要使用【yòng】的方【fāng】法就是函数 pcntl_fork,一次【cì】调用两【liǎng】次返回,在父进程【chéng】中返回【huí】子进程pid,在子【zǐ】进程中返回0,出错返回-1。
执行结果如下:
1 2 3 4 5 6 7 8 9 10 |
fork the 1th child, pid: 7326
I am the parent, pid: 7326
fork the 2th child, pid: 7327
I am the parent, pid: 7327
fork the 3th child, pid: 7328
I am the parent, pid: 7328
fork the 4th child, pid: 7329
I am the parent, pid: 7329
fork the 5th child, pid: 7330
I am the parent, pid: 7330
|
先解释一【yī】下为什么会产生10条【tiáo】打【dǎ】印结果,第一条结果【guǒ】是子进【jìn】程【chéng】打印的,第二条是在父进程打【dǎ】印的!
1、如【rú】果是在循环中创建子进程,那么子进程中【zhōng】最【zuì】后【hòu】要exit,防【fáng】止子【zǐ】进程进入循环!
2、必须【xū】等待子进程执行【háng】完任【rèn】务, 有一【yī】个【gè】简单方【fāng】法是使【shǐ】用 pcntl_wait,如果不加这个你会发现一个是执【zhí】行的顺序不【bú】固定,第二个就是【shì】创建的进【jìn】程会少于【yú】5个,但是加了你会发现【xiàn】这个完全变成并行【háng】了...上面的结果【guǒ】就是
然后找了找,发现下面这种写法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
<?php
$ids = [];
foreach (range(1, 5) as $index) {
$ids[] = $pid = pcntl_fork();
if ($pid === -1) {
echo "failed to fork!n" ;
exit ;
}
elseif ($pid)
{
echo "I am the parent, pid: $pidn" ;
}
else
{
$cid = posix_getpid();
echo "fork the {$index}th child, pid: $cidn" ;
exit ;
}
}
foreach ($ids as $i => $pid) {
if ($pid) {
pcntl_waitpid($pid, $status);
}
}
结果如下:
I am the parent, pid: 19054
fork the 1th child, pid: 19054
I am the parent, pid: 19055
fork the 2th child, pid: 19055
I am the parent, pid: 19056
fork the 3th child, pid: 19056
I am the parent, pid: 19057
I am the parent, pid: 19058
fork the 5th child, pid: 19058
fork the 4th child, pid: 19057
|
找了一张图,大体解释了总体流程:
几行代码就可以写出一个多进程程序,实现并行编程。
管理子进程:
创建好了进程,那么怎么对子进程进行管理呢?使用信号。
在计算【suàn】机科学中,信号是Unix、类Unix以及其他POSIX兼容的操作系统【tǒng】中进程间通讯的【de】一种【zhǒng】有限【xiàn】制【zhì】的【de】方式。它是一种异【yì】步的通知机制,用来提【tí】醒进程【chéng】一个事件已经发生【shēng】。
分发信号处理器:
我们【men】通过【guò】在父进程接收【shōu】子进程【chéng】传来的信号,判【pàn】断子进【jìn】程状态,来对【duì】子进程进行管理。
我们【men】需要【yào】在父进程里使【shǐ】用pcntl_signal()函数和pcntl_signal_dispatch()函数来给【gěi】各个子进程安装信号处【chù】理器。
1 2 3 4 |
pcntl_signal (int $signo , callback $handler) 安装一个【gè】信号处【chù】理器;
$signo是待处理的信号常量,callback是其处理函数
pcntl_signal_dispatch () 调用每个等待【dài】信号【hào】通过pcntl_signal()安装的处理器【qì】
|
PHP内常见的信号常量有:
1 2 3 4 |
SIGCHLD 子进【jìn】程退出成为僵尸进程会向父进【jìn】程发送此信号【hào】
SIGHUP 进程挂起
SIGTEM 进程终止
... // 其他请在手册中查看
|
安【ān】装并调用信号处理器【qì】后,一旦子【zǐ】进程【chéng】有相应的信号返【fǎn】回【huí】给父进程,父进程就可以调【diào】用【yòng】相应的callback函数对子进程处理【lǐ】;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<?php
//ctrl + cp
cntl_signal(SIGINT, function () {
fwrite(STDOUT, "receive signal: " . SIGINT . " do nothing ...n" );
});
//kill
pcntl_signal(SIGTERM, function () {
fwrite(STDOUT, "receive signal: " . SIGTERM . " I will exit!n" );
exit ;
});
while ( true ) {
pcntl_signal_dispatch();
echo "do something。。。n" ;
sleep (5);
}
|
Linux进【jìn】程信号分为很【hěn】多种,kill -l 可以【yǐ】查看,PHP里面定义了43种,咱【zán】就说说常用【yòng】的几【jǐ】种:
SIGINT 2 这个其实相对于 ctrl+c
SIGTERM 15 就是 kill 默认的参数【shù】,表示终止信号,但【dàn】是你发了【le】信【xìn】号程序【xù】不一【yī】定响应
SIGKILL 9 就是 kill -9, 表示立马终止【zhǐ】,这个【gè】信号在PHP里面是无法注【zhù】册的,所以一定能【néng】成功
看明白了这个就可以读懂上【shàng】面的例子【zǐ】了【le】,其中 pcntl_signal 是【shì】注册信号处理handler,第一个参数【shù】是你需要注册的信号,第二个是【shì】处理操作【zuò】,可以【yǐ】是【shì】匿名函数【shù】或者一个函数名,可【kě】以【yǐ】注册多【duō】个信号【hào】。pcntl_signal_dispatch 调用每个【gè】等待信号通过pcntl_signal() 安装的处理器。早期PHP还有【yǒu】一【yī】种写法是使【shǐ】用 ticks,性能非常差,php5.3之后【hòu】建【jiàn】议都使用 pcntl_signal_dispatch。
说明【míng】一下:pcntl_signal()函数【shù】仅仅是注【zhù】册信号和它的处【chù】理方法,真正接【jiē】收【shōu】到信号并调用其处理方【fāng】法的是pcntl_signal_dispatch()函数必须【xū】在循环里调用,为了【le】检测是否【fǒu】有【yǒu】新【xīn】的信号等待dispatching。
上面的【de】例子执行【háng】结果就【jiù】是当你使用 ctrl+c 的【de】话是无法终止程序的【de】,只有使用 kill pid 这种【zhǒng】形式才可以,但【dàn】是【shì】并不是立马就退出,它【tā】是代码执行到【dào】循环顶部 pcntl_signal_dispatch 地方的【de】时候【hòu】才会退出,这就保证了你使用kill杀掉进程【chéng】的时候并不会丢【diū】失数据,说【shuō】好听点这【zhè】也算【suàn】是平滑重【chóng】启吧【ba】!
处理子进程:
对子进程的处理方法有:
posix_kill():此函【hán】数并不能【néng】顾名【míng】思义,它通过向子进程【chéng】发送一个信号来操作子进程,在需要要时【shí】可以选择给子进【jìn】程发送进程终【zhōng】止信号来终止子【zǐ】进程;
pcntl_waitpid():等待或返回fork的子进程状态,如果指定的子进程在此【cǐ】函【hán】数【shù】调用时已经退出(俗【sú】称僵【jiāng】尸进程),此函【hán】数【shù】将立刻返回,并【bìng】释【shì】放子【zǐ】进程的所有系统资源,此【cǐ】进程可以避免子进程变成僵【jiāng】尸进【jìn】程,造成系【xì】统资源【yuán】浪费;
下面是两个函数的函数原型:
1 2 3 4 5 |
// 向进程 id 为$pid的进程发送$sig信号,$sig常见信号如上;
bool posix_kill ( int $pid , int $sig )
// 挂起当前进程的执行【háng】直到进【jìn】程号为$pid的进程退【tuì】出(如【rú】果$pid为-1,则等待任意【yì】一个子【zǐ】进程);
int pcntl_waitpid ( int $pid , int &$status [, int $options = 0 ] )
|
PHP多进程优点:
1.使用多进程, 子进程结束以后, 内核会负责回收资源;
2.使【shǐ】用多进【jìn】程【chéng】,子进程异常退出不会导致【zhì】整个进程Thread退【tuì】出. 父进程还有机会【huì】重建流程【chéng】.
3.一个常驻主进程, 只负责任务分发, 逻辑更清楚.
【标准版】400元/年/5用户/无限容量
【外贸版】500元/年/5用户/无限容量
其它服务:网【wǎng】站建【jiàn】设【shè】、企业邮箱、数字证书ssl、400电话、
联系方式:电话:13714666846 微信同号
声明:本【běn】站【zhàn】所【suǒ】有作【zuò】品(图文、音视频)均由用户自行上传分享,或互联网相关知识【shí】整合,仅供网友【yǒu】学习交流,若您的权利被侵【qīn】害,请【qǐng】联系 管【guǎn】理【lǐ】员 删除。
本文【wén】链【liàn】接:https://www.city96.com/article_32741.html