是早期打算机的多程序实行流程,我们可以看到在一个CPU的生命周期里面,多个程序是顺序实行的。后面的程序什么时候实行取决于前面的程序什么之后实行完。如果当前实行的程序进行IO时,CPU也只能等待,CPU的利用率极低。如果当前实行的程序占用CPU韶光很长,其他程序得不到CPU的实行权,只能卡着不动。
基于上面说的单进程有很多的缺陷,以是涌现了多道程序技能,使得程序可以并发实行。从上图可以看出来多道程序设计把CPU分成了韶光片,可以在单CPU的情形下实现并发。CPU以人无法感知的速率在一直切换实行不同的程序,这样让我们看起来像是多个程序同时在实行。
我们可以看到了每个进程有自己独立的内存、地址空间、数据栈等等,每个进程都跟其他进程互不滋扰,这便是为什么有些人在写代码都时候,说定义了一个全局的属性,但是一个进程改变了,另一个进程读取却没有变革,这便是缘故原由,每个进程有自己的独立堆栈,内存资源等。

进程状态
图中可以看到进程有不同的状态,当进程在不同状态进程处于就绪态的时候便是等待操作系统进行调度。前面我们提到过进程的切换,CPU不断的切换到不同的进程来实行,当CPU调度别的进程当前实行的进程怎么办呢?下次再调度会这个进程的之后怎么知道代码实行到哪里,都有什么数据呢?实在当CPU调度别的进程的时候,会保存当前实行进程的信息、状态,这种操作叫做高下文切换。一个进程从运行态转到休眠状态的时候,进程的现场会保存到该进程的内核栈,当这进程再次进入就运行状态的时候,CPU从这个进程的PCB内核栈读取这个现场信息规复进程。
多道程序设计把CPU实行的韶光分割成了很小的韶光片,由操作系统在不同状态的进程进行调度。但是在一个核心CPU一次只能处理一件事情,只是由于每次切换的韶光太快了,让我们认为是一起实行的。
高下文切换
当一个进程碰着壅塞的之后CPU就会切换到另一个进程实行,避免在在进程壅塞的时候摧残浪费蹂躏CPU资源。
进程不是越多越好,进程过多在高下文切换过程中摧残浪费蹂躏大量的CPU可实行韶光
并发与并行的差异:并发是某一个韶光段做多少事情,并行是同一时候同时做多件事情
孤儿进程
前面说过了,打算机仿照的是人类天下,那么这个孤儿进程跟我们人类天下一样,没有父母没有监护人的为孤儿。孤儿进程产生的缘故原由是父进程终止实行了,但是子进程依然在实行的子进程就被叫做孤儿进程。
僵尸进程
僵尸进程产生的缘故原由是由于子进程终止之后父进程没有对其进行回收,子进程的进程描述符依然保留在系统当中,以是产生了僵尸进程,僵尸进程对付操作系统是有资源摧残浪费蹂躏的。以是务必避免涌现僵尸进程,办理办法可以在父进程调用wait或者waitpid办理。
守护进程
在Linux操作系统里面我们想要实现关闭终端之后还可以运行,我们常日采取
nohup
setsid
命令后面加 &
这几种办法,我们想想在前面说过的孤儿进程彷佛就可以是现在这样的功能。我们只要父进程退出实行,子进程还是依然会实行的。这便是我们通过故意产生孤儿进程来实现守护进程的功能。
通过前面的知识我们理解了进程之后,我们接下来做一个关于进程的实战功能。
实现一个Master、Worker的进程 希望通过这个案例大家能对PHP实现进程有更深的理解。背景:在编写做事端程序的时候单进程的消费速率跟不上,这时候可以选择多进程来操作,选择多进程为了防止进程去世掉导致任务无法实行,以是采取Master、Worker进程模型实现一个进程管理。
<?phpclass SunnyProcess{ // Master进程id protected static $masterPid; // Worker进程id凑集 protected static $workerPid = []; / 设置进程名称 @String $name 进程名称 / public static function setProcessTitle($name){ if (extension_loaded('proctitle') && function_exists('setproctitle')) { @setproctitle($name); } elseif (version_compare(phpversion(), "5.5", "ge") && function_exists('cli_set_process_title')) { @cli_set_process_title($name); } } / 创建Worker进程 @Int $num Worker进程数量 / public static function forkWorker($num = 2){ for($i=0;$i<$num;$i++){ $pid = pcntl_fork(); if($pid>0){ // 父进程,把子进程id存到进程凑集 static::$workerPid[$pid] = $pid; print_r(static::$workerPid); }elseif($pid===0){ // 子进程 static::setProcessTitle("php:worker"); while(1){ // 获取当提高程id $pids = posix_getpid(); $time = microtime(true); $masterpid = static::$masterPid; echo "主进程:{$masterpid},当提高程:{$pids},当前韶光:{$time}\n"; sleep(1); } }else{ exit('进程启动失落败\n'); } } } / Master进程监控Worker进程 / public static function monitor(){ while(true){ // 挂起当提高程的实行直到一个子进程退出或吸收到一个旗子暗记 $status = 0; // 利用pcntl_wait挂起当提高程,挂起之后会壅塞不实行pcntl_wait之后的代码 // 直到pcntl_wait返回pid $pid = pcntl_wait($status, WUNTRACED); var_dump($pid);//这段代码今后的代码只有在pcntl_wait有触发的时候才会调用 pcntl_signal_dispatch(); if ($pid >= 0) { // worker康健检讨 unset(static::$workerPid[$pid]); echo "worker检讨:{$pid}\n"; static::forkWorker(1); print_r(static::$workerPid);echo "\n"; } } } public static function runAll(){ // 设置主进程名字 static::setProcessTitle("php:master"); // 获取主进程id static::$masterPid = posix_getpid(); // 创建3个子进程 static::forkWorker(3); // 监控worker进程,当子进程去世掉重新创建 static::monitor(); }}SunnyProcess::runAll();
这样可以通过PHP的创建一个较为健壮的Master-Worker进程,Master卖力管理子进程,详细事情让进程去事情。
在上面的代码中启动了一个Master进程和3个Worker进程,Master进程的事情很大略。
public static function runAll(){ // 设置主进程名字 static::setProcessTitle("php:master"); // 获取主进程id static::$masterPid = posix_getpid(); // 创建3个子进程 static::forkWorker(3); // 监控worker进程,当子进程去世掉重新创建 static::monitor();}
runAll 方法里面便是Master所做的事情
设置一个主进程的名字,也便是master进程的名字获取master主进程的id保存起来创建子进程监控子进程
public static function setProcessTitle($name){ if (extension_loaded('proctitle') && function_exists('setproctitle')) { @setproctitle($name); } elseif (version_compare(phpversion(), "5.5", "ge") && function_exists('cli_set_process_title')) { @cli_set_process_title($name); }}
setProcessTitle 方法卖力设置进程的名字
public static function forkWorker($num = 2){ for($i=0;$i<$num;$i++){ $pid = pcntl_fork(); if($pid>0){ // 父进程,把子进程id存到进程凑集 static::$workerPid[$pid] = $pid; print_r(static::$workerPid); }elseif($pid===0){ // 子进程 static::setProcessTitle("php:worker"); while(1){ // 获取当提高程id $pids = posix_getpid(); $time = microtime(true); $masterpid = static::$masterPid; echo "主进程:{$masterpid},当提高程:{$pids},当前韶光:{$time}\n"; sleep(1); } }else{ exit('进程启动失落败\n'); } }}
forkWorker 方法卖力创建子进程,由于在 runAll 方法中调用 forkWorker 方法的时候通报了一个参数值为 3 因此创建3个子进程,以是循环3次。
调用php的pcntl扩展函数 pcntl_fork 创建一个子进程,pcntl_fork 函数返回一个进程ID,如果该值大于0则为父进程,即是0则为子进程,否则失落败。
真正的业务就在在子进程去事情的,在pid===0的时候去卖力编写我们的业务代码。这里示例代码只是大略的输出一下进程id和韶光。
public static function monitor(){ while(true){ // 挂起当提高程的实行直到一个子进程退出或吸收到一个旗子暗记 $status = 0; // 利用pcntl_wait挂起当提高程,挂起之后会壅塞不实行pcntl_wait之后的代码 // 直到pcntl_wait返回pid $pid = pcntl_wait($status, WUNTRACED); var_dump($pid);//这段代码今后的代码只有在pcntl_wait有触发的时候才会调用 pcntl_signal_dispatch(); if ($pid >= 0) { // worker康健检讨 unset(static::$workerPid[$pid]); echo "worker检讨:{$pid}\n"; static::forkWorker(1); print_r(static::$workerPid);echo "\n"; } }}
monitor 方法卖力监控子进程是否正常事情,当进程去世掉之后Master进程会吸收到一个旗子暗记,调用 pcntl_wait 等待会壅塞等待旗子暗记发生,旗子暗记发生的时候会返回子进程的ID,通过子进程的ID去进程ID凑集删除掉老的ID,并且重新调用 forkWorker 方法重新创建一个新的进程。
效果演示
左边实行脚本,右边查看进程信息;可以看到启动了四个进程,分别是一个Master和三个Worker进程。
杀去世进程
kill -9 35170
杀去世进程ID为35169的Worker进程,再次查看进程信息
可以看到进程ID为35169的进程已经不存在了,被杀去世了。新多出来一个35174的进程。左边的信息框信息也可以看到打印出来的进程信息。
再次杀去世一个进程
kill -9 35170
杀去世进程ID为35170的Worker进程,再次查看进程信息
可以看到进程ID为35170的进程已经不存在了,被杀去世了。新多出来一个35197的进程。左边的信息框信息也可以看到打印出来的进程信息。
视频演示:
视频加载中...
通过上面的效果演示可以看到,我们的目的基本达到了,Worker去世掉之后Master会帮我们重新创建一个进程担保我们的业务正常运行。