이 글은 주로 PHP 다중 프로세스 프로그래밍에 대한 초기 소개를 소개합니다. 이제 특정 참고 가치가 있으므로 도움이 필요한 친구들이 참고할 수 있습니다.
EnvyNarutoninli 나루토의 그림자 클론? 예, PHP 프로그램은 섀도우 클론을 활성화할 수 있습니다! 작업을 완료하고 싶지만 하나의 프로세스가 너무 느리다고 생각되면 여러 프로세스를 사용해 보세요. 이 기사에서는 PHP 다중 프로세스의 기본 요구 사항, 다중 프로세스 생성 방법 및 기본 신호 제어 방법을 소개합니다. 당분간 프로세스 간 정보 통신 및 공유 방법은 설명하지 않습니다.
조치를 취하기 전에 M$ Windows를 사용하고 있지 않은지 확인하세요. 플랫폼(Windows가 없기 때문에). Linux/BSD/Unix는 문제가 되지 않습니다. 작업 환경을 확인한 후, 필요한 PHP 모듈이 사용 가능한지 살펴보겠습니다. 터미널을 열고 다음 명령을 입력하세요:
$ php -m
이 명령은 현재 열려 있는 모든 PHP 확장을 확인하고 인쇄합니다. pcntl
및 posix
가 있는지 확인하세요. 목록에 출력 중입니다. pcntl
和posix
是否在输出的列表中。
如果找不到pcntl
,八成是编译的时候没把这个扩展编译进去。如果你和我一样是编译安装的PHP,那么需要重新编译安装PHP。在配置的时候记得加上--enable-pcntl
参数即可。
$ cd /path/to/php_source_code_dir $ ./configure [some other options] --enable-pcntl $ make && make install
这货一般默认就会装上,只要你编译的时候没有加上--disable-posix
。
在继续之前,你还需要对Linux多进程有一点了解。多进程是咋回事呢?这里可跟火影忍者里的影分身稍微有点不同。首先,鸣人从小长到大,比如16岁,咳。有一天他发动了影分身,分出了5个他。显然,这些分身也是16岁的鸣人而不是刚出生啥也不懂就会哭的婴儿(那叫克隆)。然后,不一样的地方来了:分身们变成了独立的人各自去做各自的事,互相之间不再知道其他分身和原身都做了什么(当然不会像动画片里一样积累经验给原身啦)。除非,他们互相之间有交流,不然,只有16岁之前的事情才是他们共同的记忆。
有同学说了,老大你这不坑爹呢么?我又没看过火影忍者!那你去看一遍好了……
最后,预备知识完了,就是大致了解一下主进程开出来的子进程是怎么回事。子进程的代码和主进程是完全一样的,还有一部分一样的东西就是直到发动影分身之前执行的所有内容。具体请参见《操作系统》课程。
所以呢,没有点基础知识怎么能理解卷轴里的内容呢?打开卷轴首先看到了一个单词:fork。
叉子?叉子是分岔的,一个变多个嘛!差不多就是这个意思。创建子进程就用这个命令。这里需要用到pcntl_fork()
函数。(可以先简单看一下PHP手册关于这个函数的介绍。)创建一个PHP脚本:
$pid = pcntl_fork(); // 一旦调用成功,事情就变得有些不同了 if ($pid == -1) { die('fork failed'); } else if ($pid == 0) {} else {}
pcntl_fork()
函数创建一个子进程,子进程和父进程唯一的区别就是PID(进程ID)和PPID(父进程ID)不同。在终端下查看进程用ps
命令(问问man看ps怎么用:man ps
)。当函数返回值为-1的时候,说明fork失败了。试试在if
前面加一句:echo $pid . PHP_EOL;
。运行你的脚本,输出可能像下面这样(结果说明子进程和父进程的代码是相同的):
67789 # 这个是父进程打印的 0 # 这个是子进程打印的
pcntl_fork()
函数调用成功后,在父进程中会返回子进程的PID,而在子进程中返回的是0。所以,下面直接用if
分支来控制父进程和子进程做不同的事。
然后我们来说说鸣人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;}
输出的结果可能是这样:
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
再强调一下,pcntl_fork()
调用成功以后,一个程序变成了两个程序:一个程序得到的$pid
变量值是0,它是子进程;另一个程序得到的$pid
的值大于0,这个值是子进程的PID,它是父进程。在下面的分支语句中,由于$pid
pcntl
을 찾을 수 없으면 이 확장 프로그램이 컴파일 중에 컴파일되지 않았을 가능성이 높습니다. 저처럼 PHP를 컴파일하고 설치했다면, PHP를 다시 컴파일하고 설치해야 합니다. 구성할 때 --enable-pcntl
매개변수를 추가하는 것을 잊지 마세요. #🎜🎜#// 定义一个处理器,接收到SIGINT信号后只输出一行信息 function signalHandler($signal) { if ($signal == SIGINT) { echo 'signal received' . PHP_EOL; }} // 信号注册:当接收到SIGINT信号时,调用signalHandler()函数pcntl_signal(SIGINT, 'signalHandler');while (true) { sleep(1); // do something pcntl_signal_dispatch(); // 接收到信号时,调用注册的signalHandler()}
--disable-posix
를 추가하지 않는 한 일반적으로 기본적으로 설치됩니다. #🎜🎜##🎜🎜#2. 사전 지식#🎜🎜##🎜🎜#계속하기 전에 Linux 다중 프로세스에 대한 약간의 이해도 필요합니다. 다중 처리에서는 무슨 일이 일어나고 있나요? 이것은 나루토의 섀도우 클론과 약간 다릅니다. 우선 나루토는 16세 정도의 어린 나이부터 기침을 하며 자랐다. 어느 날 그는 자신의 섀도우 클론을 활성화하고 자신을 다섯 개로 나누었습니다. 분명히 이 클론 역시 16세 나루토이며, 태어나자마자 울고 아무것도 이해하지 못하는 아기(이를 복제라고 함)만이 아닙니다. 그렇다면 차이점은 다음과 같습니다. 클론들은 각자의 일을 하는 독립적인 사람들이 되었고, 그들은 더 이상 다른 클론들과 원래의 몸이 무엇을 했는지 알지 못합니다(물론 만화에서처럼 경험치를 축적하지는 않을 것입니다). . 서로 소통하지 않는 한, 16세 이전의 일들만이 그들의 공통 기억이다. #🎜🎜##🎜🎜#어떤 반 친구들이 그러더군요, 선생님, 바람 피우는 거 아닙니까? 나 나루토 안봤어! 그럼 한번 살펴보세요...#🎜🎜##🎜🎜#마지막으로 준비 지식은 끝났습니다. 메인 프로세스에서 시작되는 하위 프로세스에 대한 전반적인 이해만 하시면 됩니다. 하위 프로세스의 코드는 기본 프로세스와 정확히 동일하며, 동일한 부분의 다른 부분은 섀도우 클론이 시작될 때까지 실행되는 모든 것입니다. 자세한 내용은 "운영 체제" 과정을 참조하세요. #🎜🎜##🎜🎜#3. 섀도우 클론의 예술#🎜🎜##🎜🎜#그렇다면 기본적인 지식 없이 두루마리의 내용을 어떻게 이해할 수 있습니까? 두루마리를 열었을 때 처음으로 포크라는 단어가 보였습니다. #🎜🎜#pcntl_fork()
함수를 사용해야 합니다. (먼저 PHP 매뉴얼에서 이 함수에 대한 소개를 간략하게 살펴볼 수 있습니다.) PHP 스크립트 만들기: #🎜🎜#for ($i = 0; $i < 1000000; $i++) { echo $i . PHP_EOL; usleep(100000);}#🎜🎜#
pcntl_fork()
이 함수는 하위 프로세스를 만듭니다. , 하위 프로세스 및 상위 프로세스 유일한 차이점은 PID(프로세스 ID)와 PPID(상위 프로세스 ID)가 다르다는 것입니다. 터미널에서 프로세스를 보려면 ps
명령을 사용하세요(man에게 ps 사용 방법을 문의하세요: man ps
). 함수가 -1을 반환하면 포크가 실패했음을 의미합니다. if
앞에 문장을 추가해 보세요: echo $pid ;
. 스크립트를 실행하면 출력은 다음과 같을 수 있습니다(결과는 하위 프로세스와 상위 프로세스의 코드가 동일함을 보여줍니다): #🎜🎜#rrreee#🎜🎜#pcntl_fork()
함수 호출이 성공한 후 상위 프로세스에는 하위 프로세스의 PID가 반환되고 하위 프로세스에는 0이 반환됩니다. 따라서 if
분기를 직접 사용하여 상위 프로세스와 하위 프로세스가 서로 다른 작업을 수행하도록 제어해 보겠습니다. #🎜🎜#pcntl_fork()
호출이 성공한 후 하나의 프로그램은 두 개의 프로그램이 됩니다. 한 프로그램에서 얻은 $pid
변수는 0이며 이는 하위 프로세스입니다. 다른 프로그램에서 얻은 $pid
의 값은 0보다 크며 이 값은 상위 프로세스인 하위 프로세스의 PID입니다. 아래 분기문에서는 $pid
값이 다르기 때문에 서로 다른 코드가 실행됩니다. 다시 한 번 강조하겠습니다. 하위 프로세스의 코드는 상위 프로세스의 코드와 동일합니다. 따라서 분기 문을 통해 서로 다른 작업을 할당해야 합니다. #🎜🎜#刚刚有man ps
么?一般我习惯用ps aux
加上grep
命令来查找运行着的后台进程。其中有一列STAT
,标识了每个进程的运行状态。这里,我们关注状态Z
:僵尸(Zombie)。当子进程比父进程先退出,而父进程没对其做任何处理的时候,子进程将会变成僵尸进程。Oops,又跟火影里的影分身不一样了。鸣人的影分身被干死了以后就自动消失了,但是这里的子进程分身死了话还留着一个空壳在,直到父进程回收它。僵尸进程虽然不占什么内存,但是很碍眼,院子里一堆躺着的僵尸怎么都觉得怪怪的。(别忘了它们还占用着PID)
一般来说,在父进程结束之前回收挂掉的子进程就可以了。在pcntl
扩展里面有一个pcntl_wait()
函数,它会将父进程挂起,直到有一个子进程退出为止。如果有一个子进程变成了僵尸的话,它会立即返回。所有的子进程都要回收,所以多等等也没关系啦!
如果父进程先挂了怎么办?会发生什么?什么也不会发生,子进程依旧还在运行。但是这个时候,子进程会被交给1号进程,1号进程成为了这些子进程的继父。1号进程会很好地处理这些进程的资源,当它们结束时1号进程会自动回收资源。所以,另一种处理僵尸进程的临时办法是关闭它们的父进程。
一般多进程的事儿讲到上面就完了,可是信号在系统中确实是一个非常重要的东西。信号就是信号灯,点亮一个信号灯,程序就会做出反应。这个你一定用过,比如说在终端下运行某个程序,等了半天也没什么反应,可能你会按 Ctrl+C 来关闭这个程序。实际上,这里就是通过键盘向程序发送了一个中断的信号:SIGINT。有时候进程失去响应了还会执行kill [PID]
命令,未加任何其他参数的话,程序会接收到一个SIGTERM信号。程序收到上面两个信号的时候,默认都会结束执行,那么是否有可能改变这种默认行为呢?必须能啊!
人是活的程序也是活的,只不过程序需要遵循人制定的规则来运行。现在开始给信号重新设定规则,这里用到的函数是pcntl_signal()
(继续之前为啥不先查查PHP手册呢?)。下面这段程序将给SIGINT重新定义行为,注意看好:
// 定义一个处理器,接收到SIGINT信号后只输出一行信息 function signalHandler($signal) { if ($signal == SIGINT) { echo 'signal received' . PHP_EOL; }} // 信号注册:当接收到SIGINT信号时,调用signalHandler()函数pcntl_signal(SIGINT, 'signalHandler');while (true) { sleep(1); // do something pcntl_signal_dispatch(); // 接收到信号时,调用注册的signalHandler()}
执行一下,随时按下 Ctrl+C 看看会发生什么事。
说明一下:pcntl_signal()
函数仅仅是注册信号和它的处理方法,真正接收到信号并调用其处理方法的是pcntl_signal_dispatch()
函数。试试把// do something
替换成下面这段代码:
for ($i = 0; $i < 1000000; $i++) { echo $i . PHP_EOL; usleep(100000);}
在终端下执行这个脚本,当它不停输出数字的时候尝试按下 Ctrl+C 。看看程序有什么响应?嗯……什么都没有,除了屏幕可能多了个^C
以外,程序一直在不停地输出数字。因为程序一直没有执行到pcntl_signal_dispatch()
,所以就并没有调用signalHandler()
,所以就没有输出signal received
。
如果认真看了PHP文档,会发现pcntl_signal_dispatch()
这个函数是PHP 5.3以上才支持的,如果你的PHP版本大于5.3,建议使用这个方法调用信号处理器。5.3以下的版本需要在注册信号之前加一句:declare(ticks = 1);
表示每执行一条低级指令,就检查一次信号,如果检测到注册的信号,就调用其信号处理器。想想就挺不爽的,干嘛一直都检查?还是在我们指定的地方检查一下就好。
现在我们回到子进程回收的问题上(差点忘了= =")。当你的一个子进程挂了(或者说是结束了),但是父进程还在运行中并且可能很长一段时间不会退出。一个僵尸进程从此站起来了!这时,保护伞公司(内核)发现它的地盘里出现了一个僵尸,这个僵尸是谁儿子呢?看一下PPID就知道了。然后,内核给PPID这个进程(也就是僵尸进程的父进程)发送一个信号:SIGCHLD。然后,你知道怎么在父进程中回收这个子进程了么?提示一下,用pcntl_wait()
函数。
방금 kill
명령을 주의 깊게 수행하셨기를 바랍니다. 실제로 프로세스에 신호를 보냅니다. PHP에서는 posix_kill()
함수를 호출하여 동일한 효과를 얻을 수도 있습니다. 이를 통해 상위 프로세스에서 다른 하위 프로세스의 실행을 제어할 수 있습니다. 예를 들어 상위 프로세스가 끝나기 전에 모든 하위 프로세스를 닫으려면 fork
시 상위 프로세스에 있는 모든 하위 프로세스의 PID를 기록하고 상위 프로세스보다 먼저 하위 프로세스에 종료 신호를 순서대로 보냅니다. 프로세스가 종료됩니다. kill
命令。它其实就是向进程发送信号,在PHP中也可以调用posix_kill()
函数来达到相同的效果。有了它就可以在父进程中控制其他子进程的运行了。比如在父进程结束之前关闭所有子进程,那么fork
的时候在父进程记录所有子进程的PID,父进程结束之前依次给子进程发送结束信号即可。
PHP的多进程跟C还是挺像的,搞明白了以后用其他语言写的话也大同小异差不多都是这么个情况。如果有空的话,尝试写一个小程序,切身体会一下个中滋味:
16岁的鸣人发送影分身,分出5个分身
每个分身随机生存10到30秒,每秒都输出点什么
保证原身能感受到分身的结束,然后开动另一个分身,保证最多有5个分身
不使用nohup
,让原身在终端关闭后依旧能够运行
把分身数量(5)写进一个配置文件里,当给原身发送信号(可以考虑用SIGUSR1或SIGUSR2)时,原身读取配置文件并更新允许的分身最大数量
如果分身多了,关闭几个;如果少了,再分出来几个
提示:
用while
循环保证进程运行,注意sleep
以免100%的CPU占用
运行进程的终端被关闭时,程序会收到一个SIGHUP信号
可以用parse_ini_file()
각 클론은 10~30초 동안 무작위로 생존하고 매초마다 무언가를 출력합니다
nohup
를 사용하지 마세요. let 터미널을 닫은 후에도 원본 본문은 계속 실행될 수 있습니다
sleep
에 주의하세요#🎜🎜##🎜🎜#parse_ini_file()
함수를 사용하여 INI 구성 파일을 구문 분석할 수 있습니다 #🎜🎜##🎜🎜##🎜🎜##🎜🎜#위 글은 모두의 공부에 도움이 되었으면 좋겠습니다. 더 많은 관련 내용은 PHP 중국어 홈페이지를 주목해주세요! #🎜🎜##🎜🎜#관련 추천: #🎜🎜##🎜🎜##🎜🎜#PHP 다형성의 이해에 대하여#🎜🎜##🎜🎜##🎜🎜##🎜🎜##🎜🎜#PHP 파일 프로그래밍 소개#🎜🎜##🎜🎜##🎜🎜#위 내용은 PHP 다중 프로세스 프로그래밍에 대한 예비 소개의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!