0

0

php中兑现多进程

php中文网

php中文网

发布时间:2016-06-13 10:38:58

|

917人浏览过

|

来源于php中文网

原创

php中实现多进程

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

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

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

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

?

立即学习PHP免费学习笔记(深入)”;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
declare(ticks = 1);??
??
function signal_handler($signal) {
????print "Caught SIGALRM\n";
????pcntl_alarm(5);
}??
??
pcntl_signal(SIGALRM, "signal_handler", true);
pcntl_alarm(5);??
??
for(;;) {
}??
??
?>

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

?
1
2
3
4
5
6
7
8
9
10
11
$dir = '/home/shankka/';
$cmd = 'ls';
$option = '-l';
$pathtobin = '/bin/ls';??
??
$arg = array($cmd, $option, $dir);??
??
pcntl_exec($pathtobin, $arg);
echo '123';??? //不会执行到该行
?>

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

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

?

立即学习PHP免费学习笔记(深入)”;

1
2
3
4
5
6
7
8
9
10
11
12
$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
}
?>

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

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这些函数。
例如:

?

立即学习PHP免费学习笔记(深入)”;

1
2
3
4
5
6
7
8
9
10
11
12
$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);
}
?>

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

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

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

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

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

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

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

除了上面的这两个方法外,还有一些实现上比较复杂的方法,比如建立管道或socket来进行时时的监控等等。

PHP多进程采集数据的例子

ecshop
ecshop

本版本全面兼容php5.6+,并且修复了许多官方程序的低级代码bug。在apache 2.4.17+php5.6.15环境下测试通过,人格保证无毒无木马,仅仅是一名ecshop热爱者心血来潮之作。ecshop编译更新日志:1、加入最新官方补丁。2、修改数据库连接底层为mysqli, 现在完美无缺了。3、再次对所有代码进行细节修复。4、adminers更新至1.1.2, 在线管理数据库的神器。5、测

下载

?

立即学习PHP免费学习笔记(深入)”;

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
/**
* Project: Signfork: php多线程库
* File:??? Signfork.class.php
*/
??
class Signfork{
??/**
???* 设置子进程通信文件所在目录
???* @var string
???*/
??private $tmp_path='/tmp/';
??
/**
??* Signfork引擎主启动方法
??* 1、判断$arg类型,类型为数组时将值传递给每个子进程;类型为数值型时,代表要创建的进程数.
??* @param object $obj 执行对象
??* @param string𞓜array $arg 用于对象中的__fork方法所执行的参数
??* 如:$arg,自动分解为:$obj->__fork($arg[0])、$obj->__fork($arg[1])...
??* @return array? 返回?? array(子进程序列=>子进程执行结果);
??*/
??public function run($obj,$arg=1){
????if(!method_exists($obj,'__fork')){
??????exit("Method '__fork' not found!");
????}
??
????if(is_array($arg)){
?????$i=0;
?????foreach($arg as $key=>$val){
???????$spawns[$i]=$key;
???????$i++;
???????$this->spawn($obj,$key,$val);
?????}
?????$spawns['total']=$i;
????}elseif($spawns=intval($arg)){
??????for($i = 0; $i $spawns; $i++){
????????$this->spawn($obj,$i);
??????}
????}else{
??????exit('Bad argument!');
????}
??
???if($i>1000) exit('Too many spawns!');
??????return $this->request($spawns);
???}
??
??/**
???* Signfork主进程控制方法
???* 1、$tmpfile 判断子进程文件是否存在,存在则子进程执行完毕,并读取内容
???* 2、$data收集子进程运行结果及数据,并用于最终返回
???* 3、删除子进程文件
???* 4、轮询一次0.03秒,直到所有子进程执行完毕,清理子进程资源
???* @param? string𞓜array $arg 用于对应每个子进程的ID
???* @return array? 返回?? array([子进程序列]=>[子进程执行结果]);
???*/
???private function request($spawns){
?????$data=array();
?????$i=is_array($spawns)?$spawns['total']:$spawns;
?????for($ids = 0; $ids$i; $ids++){
???????while(!($cid=pcntl_waitpid(-1, $status, WNOHANG)))usleep(30000);
???????$tmpfile=$this->tmp_path.'sfpid_'.$cid;
???????$data[$spawns['total']?$spawns[$ids]:$ids]=file_get_contents($tmpfile);
???????unlink($tmpfile);
?????}
?????return $data;
???}
??
/**
??* Signfork子进程执行方法
??* 1、pcntl_fork 生成子进程
??* 2、file_put_contents 将'$obj->__fork($val)'的执行结果存入特定序列命名的文本
??* 3、posix_kill杀死当前进程
??* @param object $obj??????? 待执行的对象
??* @param object $i??????????????? 子进程的序列ID,以便于返回对应每个子进程数据
??* @param object $param 用于输入对象$obj方法'__fork'执行参数
??*/
??private function spawn($obj,$i,$param=null){
????if(pcntl_fork()===0){
??????$cid=getmypid();
??????file_put_contents($this->tmp_path.'sfpid_'.$cid,$obj->__fork($param));
??????posix_kill($cid, SIGTERM);
??????exit;
????}
??}
}
?>

php在pcntl_fork()后生成的子进程(通常为僵尸进程)必须由pcntl_waitpid()函数进行资源释放。但在 pcntl_waitpid()不一定释放的就是当前运行的进程,也可能是过去生成的僵尸进程(没有释放);也可能是并发时其它访问者的僵尸进程。但可以使用posix_kill($cid, SIGTERM)在子进程结束时杀掉它。

子进程会自动复制父进程空间里的变量。

PHP多进程编程示例2

?

立即学习PHP免费学习笔记(深入)”;

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
//.....
//需要安装pcntl的php扩展,并加载它
if(function_exists("pcntl_fork")){
???//生成子进程
??$pid = pcntl_fork();
??if($pid == -1){
????die('could not fork');
??}else{
????if($pid){
??????$status = 0;
??????//阻塞父进程,直到子进程结束,不适合需要长时间运行的脚本,可使用pcntl_wait($status, 0)实现非阻塞式
??????pcntl_wait($status);
??????// parent proc code
??????exit;
????}else{
??????// child proc code
??????//结束当前子进程,以防止生成僵尸进程
??????if(function_exists("posix_kill")){
????????posix_kill(getmypid(), SIGTERM);
??????}else{
????????system('kill -9'. getmypid());
??????}
??????exit;
????}
??}
}else{
???// 不支持多进程处理时的代码在这里
}
//.....
?>

如果不需要阻塞进程,而又想得到子进程的退出状态,则可以注释掉pcntl_wait($status)语句,或写成:

?

立即学习PHP免费学习笔记(深入)”;

1
2
3
4
5
pcntl_wait($status, 1);
//或
pcntl_wait($status, WNOHANG);
?>

在上面的代码中,如果父进程退出(使用exit函数退出或redirect),则会导致子进程成为僵尸进程(会交给init进程控制),子进程不再执行。

僵尸进程是指的父进程已经退出,而该进程dead之后没有进程接受,就成为僵尸进程.(zombie)进程。任何进程在退出前(使用exit退出) 都会变成僵尸进程(用于保存进程的状态等信息),然后由init进程接管。如果不及时回收僵尸进程,那么它在系统中就会占用一个进程表项,如果这种僵尸进程过多,最后系统就没有可以用的进程表项,于是也无法再运行其它的程序。

预防僵尸进程有以下几种方法:

1. 父进程通过wait和waitpid等函数使其等待子进程结束,然后再执行父进程中的代码,这会导致父进程挂起。上面的代码就是使用这种方式实现的,但在WEB环境下,它不适合子进程需要长时间运行的情况(会导致超时)。
使用wait和waitpid方法使父进程自动回收其僵尸子进程(根据子进程的返回状态),waitpid用于临控指定子进程,wait是对于所有子进程而言。
2. 如果父进程很忙,那么可以用signal函数为SIGCHLD安装handler,因为子进程结束后,父进程会收到该信号,可以在handler中调用wait回收
3. 如果父进程不关心子进程什么时候结束,那么可以用signal(SIGCHLD, SIG_IGN)通知内核,自己对子进程的结束不感兴趣,那么子进程结束后,内核会回收,并不再给父进程发送信号,例如:

?

立即学习PHP免费学习笔记(深入)”;

1
2
3
4
5
pcntl_signal(SIGCHLD, SIG_IGN);
$pid = pcntl_fork();
//....code
?>

4. 还有一个技巧,就是fork两次,父进程fork一个子进程,然后继续工作,子进程再fork一个孙进程后退出,那么孙进程被init接管,孙进程结束后,init会回收。不过子进程的回收还要自己做。下面是一个例子:

?
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
#include "apue.h"
#include
??
int main(void){
pid_t??? pid;
??
if ((pid = fork())
???err_sys("fork error");
} else if (pid == 0){???? /**//* first child */
??if ((pid = fork())
?????err_sys("fork error");
??}elseif(pid > 0){
?????exit(0);??? /**//* parent from second fork == first child */
??}
??
??/**
???* We're the second child; our parent becomes init as soon
???* as our real parent calls exit() in the statement above.
???* Here's where we'd continue executing, knowing that when
???* we're done, init will reap our status.
???*/
???sleep(2);
???printf("second child, parent pid = %d ", getppid());
???exit(0);
}
??
if (waitpid(pid, NULL, 0) != pid)? /**//* wait for first child */
??err_sys("waitpid error");
??
/**
?* We're the parent (the original process); we continue executing,
?* knowing that we're not the parent of the second child.
?*/
?exit(0);
}

在fork()/execve()过程中,假设子进程结束时父进程仍存在,而父进程fork()之前既没安装SIGCHLD信号处理函数调用 waitpid()等待子进程结束,又没有显式忽略该信号,则子进程成为僵尸进程,无法正常结束,此时即使是root身份kill-9也不能杀死僵尸进程。补救办法是杀死僵尸进程的父进程(僵尸进程的父进程必然存在),僵尸进程成为”孤儿进程”,过继给1号进程init,init会定期调用wait回收清理这些父进程已退出的僵尸子进程。

所以,上面的示例可以改成:

?

立即学习PHP免费学习笔记(深入)”;

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
40
41
42
43
44
45
46
47
48
49
//.....
//需要安装pcntl的php扩展,并加载它
if(function_exists("pcntl_fork")){
?//生成第一个子进程
$pid = pcntl_fork();? //$pid即所产生的子进程id
if($pid == -1){
??//子进程fork失败
??die('could not fork');
}else{
??if($pid){
????//父进程code
????sleep(5);? //等待5秒
????exit(0); //或$this->_redirect('/');
??}else{
????//第一个子进程code
????//产生孙进程
????if(($gpid = pcntl_fork()) ////$gpid即所产生的孙进程id
??????//孙进程产生失败
??????die('could not fork');
????}elseif($gpid > 0){
??????//第一个子进程code,即孙进程的父进程
??????$status = 0;
??????$status = pcntl_wait($status); //阻塞子进程,并返回孙进程的退出状态,用于检查是否正常退出
??????if($status ! = 0) file_put_content('filename', '孙进程异常退出');
??????//得到父进程id
??????//$ppid =? posix_getppid(); //如果$ppid为1则表示其父进程已变为init进程,原父进程已退出
??????//得到子进程id:posix_getpid()或getmypid()或是fork返回的变量$pid
??????//kill掉子进程
??????//posix_kill(getmypid(), SIGTERM);
??????exit(0);
????}else{ //即$gpid == 0
??????//孙进程code
??????//....
??????//结束孙进程(即当前进程),以防止生成僵尸进程
??????if(function_exists('posix_kill')){
?????????posix_kill(getmypid(), SIGTERM);
??????}else{
?????????system('kill -9'. getmypid());
??????}
??????exit(0);
????}
??}
}
}else{
?// 不支持多进程处理时的代码在这里
}
//.....
?>

怎样产生僵尸进程的
一个进程在调用exit命令结束自

相关文章

PHP速学教程(入门到精通)
PHP速学教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

php

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
C++ 设计模式与软件架构
C++ 设计模式与软件架构

本专题深入讲解 C++ 中的常见设计模式与架构优化,包括单例模式、工厂模式、观察者模式、策略模式、命令模式等,结合实际案例展示如何在 C++ 项目中应用这些模式提升代码可维护性与扩展性。通过案例分析,帮助开发者掌握 如何运用设计模式构建高质量的软件架构,提升系统的灵活性与可扩展性。

9

2026.01.30

c++ 字符串格式化
c++ 字符串格式化

本专题整合了c++字符串格式化用法、输出技巧、实践等等内容,阅读专题下面的文章了解更多详细内容。

9

2026.01.30

java 字符串格式化
java 字符串格式化

本专题整合了java如何进行字符串格式化相关教程、使用解析、方法详解等等内容。阅读专题下面的文章了解更多详细教程。

8

2026.01.30

python 字符串格式化
python 字符串格式化

本专题整合了python字符串格式化教程、实践、方法、进阶等等相关内容,阅读专题下面的文章了解更多详细操作。

3

2026.01.30

java入门学习合集
java入门学习合集

本专题整合了java入门学习指南、初学者项目实战、入门到精通等等内容,阅读专题下面的文章了解更多详细学习方法。

20

2026.01.29

java配置环境变量教程合集
java配置环境变量教程合集

本专题整合了java配置环境变量设置、步骤、安装jdk、避免冲突等等相关内容,阅读专题下面的文章了解更多详细操作。

17

2026.01.29

java成品学习网站推荐大全
java成品学习网站推荐大全

本专题整合了java成品网站、在线成品网站源码、源码入口等等相关内容,阅读专题下面的文章了解更多详细推荐内容。

19

2026.01.29

Java字符串处理使用教程合集
Java字符串处理使用教程合集

本专题整合了Java字符串截取、处理、使用、实战等等教程内容,阅读专题下面的文章了解详细操作教程。

3

2026.01.29

Java空对象相关教程合集
Java空对象相关教程合集

本专题整合了Java空对象相关教程,阅读专题下面的文章了解更多详细内容。

6

2026.01.29

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PHP课程
PHP课程

共137课时 | 10.3万人学习

JavaScript ES5基础线上课程教学
JavaScript ES5基础线上课程教学

共6课时 | 11.2万人学习

PHP新手语法线上课程教学
PHP新手语法线上课程教学

共13课时 | 0.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号