当前位置: 首页 > 后端技术 > PHP

PHP多进程初探---再说daemon进程

时间:2023-03-30 01:11:41 PHP

【原文地址:https://blog.ti-node.com/blog...】其实我说的是daemon进程以前用过,不过涉及的原理太多了,不过不影响使用。今天打算多聊聊daemon进程的事情。本质上,如果只是简单的实现和使用daemon进程,可以忽略。的确,*NIX真是博大精深,看得越深,越能发现它的表盘。原理往往很枯燥,大家都不喜欢看,但这并不影响我坚持把自己对这些东西的理解写出来。三个概念,理(bei)解(song):进程组。一堆相关的进程可以组成一个进程组,每个进程组都会有一个组ID(正整数),每个进程组都会有一个组长进程,组长进程的ID等于进程组ID。组长进程可以创建新的进程组和该进程组中的其他进程。一个进程组有一个生命周期。即使组长进程死了,组中也只有其他活跃的成员,所以即使进程组还活着,也只有组中最后一个活跃的成员死了。这是真的。它就这样消失了。会议。一堆相关的进程组组成一个会话。在*NIX下,通过setsid()创建一个新会话。但值得注意的是,组长进程不能创建会话。简单理解就是在groupleader进程中,执行setsid函数会报错,这个很重要。所以一般leader进程执行fork,然后主进程退出,因为子进程的进程ID是新分配的,子进程的进程组ID继承了父进程,所以子进程注定是不可能的成为leader进程,从而保证setsid函数可以在子进程中执行。当执行setsid函数时,一般会发生以下三件重要的事情:进程会创建一个新的进程组,进程会成为进程组的leader(或者你可以认为是升职)和进程会创建一个会话组,并成为会话的会话组长进程(会话组长进程就是创建会话的进程)。该进程将失去控制终端。如果进程没有控制终端,那就完了(liao)。如果是这样,该进程也将与控制终端分离并与其失去联系。控制终端。每个session可能有一个控制终端(看玄学,可以暂时理解为一种暗黑命令行窗口),与控制终端建立连接的sessionleader进程称为控制进程。结合linux命令ps查看以上概念的恩怨,看看我们常用的ps-opid,ppid,pgid,sid,comm|less执行结果:第一行是PID、PPID、PGID、SID、COMMAND,依次是进程ID、进程父进程ID、进程组ID、会话ID、命令。通过最后一栏,我们知道第二行是bash,也就是bashshell进程。它的进程ID是15793,它的父进程是13291,它的进程组ID是15793,它的会话ID也会是15793。结合前面的概念,我们可以知道bashshell是进程组的领导者。第三行是ps命令的进程,它的进程ID是15816,它是从bash进程中fork出来的,所以它的父进程ID是15793,那么它的组ID是15816,它的会话ID还是15793.最后一行是less命令的过程。它的进程ID是15817,它也是由bash进程fork出来的,所以它的父进程ID也是15793,它的组ID是15816,它的会话ID还是15793。。简单总结:以上三个进程组成两个进程组,bash本身就是一个组,组ID为15793,组长进程为bash本身;ps和less是一个group,groupID为15816,groupleader进程为ps进程以上三个进程属于同一个session,sessionID为15793,session的第一个进程为bash进程(以定)。控制终端就是打开的终端窗口,与之关联的控制进程就是bash进程。通过这样的分析,是不是感觉更能接受一点?那么,这个我唠叨了很久,跟daemon进程有什么关系呢?啦啦啦,引入代码直接分析:$pid=pcntl_fork();if($pid<0){exit('forkerror.');}elseif($pid>0){//主流程exitsexit();}//子进程继续执行//最关键的一步来了,执行setsid函数!if(!posix_setsid()){exit('setsiderror.');}//理论上,一次fork就够了//然而,对于第二次fork,这里的历史渊源是这样的:在基于系统V的系统中,通过再次fork,父进程退出,子进程继续,保证形成的daemon进程永远不会成为sessionleader进程,不会有控制终端。$pid=pcntl_fork();if($pid<0){exit('forkerror');}elseif($pid>0){//主进程退出exit;}//子进程继续执行//啦啦啦,啦啦啦,啦啦啦,变成daemon了,happycli_set_process_title('testtesttest');//sleep1000000,防止进程挂掉sleep(1000000);将以上文件保存为daemon.php,然后执行phpdaemon.php,使用ps-aux|greptestte,如果没有大问题,应该可以看到这个进程在后台运行。那么为什么第一步是分叉呢?因为调用setsid的进程不可能是组长进程(文章开头那些无聊的知识你需要吗?),所以必须fork一次,然后直接退出主进程,留下子进程。因为子进程一定不能是leader进程,所以子进程可以调用setsid。调用setsid会产生三种现象:新建会话并成为会话组长进程,创建进程组并成为组长进程,离开控制终端。啦啦啦,明白文章开头那些枯燥的知识是为了什么?然而实际上,上面的代码只完成了一个标准daemon的80%,还有20%需要我们进一步完善。那么,需要完善什么呢?让我们修改上面的代码,让程序在最后的代码段中做一些文本输出:$pid=pcntl_fork();if($pid<0){exit('forkerror.');}elseif($pid>0){//主进程退出exit();}//子进程继续执行//最关键的一步来了,执行setsid函数!if(!posix_setsid()){exit('setsiderror.');}//理论上,一次fork就够了//然而,对于第二次fork,这里的历史渊源是这样的:在基于系统V的系统中,通过再次fork,父进程退出,子进程继续,保证形成的daemon进程永远不会成为sessionleader进程,不会有控制终端。$pid=pcntl_fork();if($pid<0){exit('forkerror');}elseif($pid>0){//主进程退出exit;}//子进程继续执行//laLala,lala,lala,变成daemon了,happycli_set_process_title('testtesttest');//循环1000次,每次sleep1s,输出一个字符testfor($i=1;$i<=1000;$i++){睡眠(1);echo"test".PHP_EOL;}将文件保存为daemon.php,然后用phpdaemon.php执行文件。那么请问有没有什么奇怪的现象,大概类似下图:即使按了Ctrl+C也不行,终端一直在输出test,只能关闭当前终端窗口再打开,但是,这不符合社会主义主流价值观。因此,我们要解决标准输出和错误输出。我们的守护程序不能再使用终端窗口作为默认的标准输出。二是将当前工作目录更改为根目录。否则可能会出现下面的问题,即如果父进程的工作目录是挂载目录,那么子进程会继承父进程的工作目录。当子进程已经被daemon化后,就会发生悲剧:即原来挂载的目录虽然不再使用,但是无法用umount卸载,非常悲剧。最后一个问题是在第一次fork之后设置umask(0)以避免一些权限问题。所以更完整的代码如下://设置umask为0,使得当前进程创建的文件权限为777umask(0);$pid=pcntl_fork();if($pid<0){exit('forkerror.');}elseif($pid>0){//主进程退出exit();}//子进程继续执行//最关键的一步是执行setsid函数!if(!posix_setsid()){exit('setsiderror.');}//理论上,一次fork就够了//然而,对于第二次fork,这里的历史渊源是这样的:在基于系统V的系统中,通过再次fork,父进程退出,子进程继续,保证形成的daemon进程永远不会成为sessionleader进程,不会有控制终端。$pid=pcntl_fork();if($pid<0){exit('forkerror');}elseif($pid>0){//主进程退出exit;}//子进程继续执行//啦啦啦,啦啦,啦啦,变成daemon了,happycli_set_process_title('testtesttest');//一般服务器软件都有写配置项,比如运行在debug模式或者daemon模式。如果是在debug模式下运行,大部分的标准输出和错误输出都是直接输出到当前终端。如果以守护进程的形式运行,那么错误输出和标准输出可能会分别输出到两个不同的配置文件中。//连工作目录都是一个配置项,可以通过php函数chdir修改当前工作目录chdir($dir);[原文地址:https://blog.ti-node.com/blog...]

最新推荐
猜你喜欢