干巴巴地叨逼叨了这么久,时候表演真正的技术了!
做个高端点儿的玩意吧,假如我们要做一个任务系统,这个系统可以在后台帮我们完成一大波(注意是一大波)数据的处理,那么我们自然想到,多开几个进程分开处理这些数据,同时我们不能执行了php task.php后终端挂起,万一一不小心关闭了终端都会导致任务失败,所以我们还要实现程序的daemon化。好啦,开始了!
首先,我们第一步就得将程序daemon化了!
  1. // 设置umask为0,这样,当前进程创建的文件权限则为777
  2. umask( 0 );
  3. $pid = pcntl_fork();
  4. if( $pid < 0 ){
  5. exit('fork error.');
  6. } else if( $pid > 0 ) {
  7. // 主进程退出
  8. exit();
  9. }
  10. // 子进程继续执行
  11. // 最关键的一步来了,执行setsid函数!
  12. /*
  13. http://linux.die.net/man/2/setsid
  14. [setsid详解][1] 主要目的脱离终端控制,自立门户。
  15. 创建一个新的会话,而且让这个pid统治这个会话,他既是会话组长,也是进程组长。
  16. 而且谁也没法控制这个会话,除了这个pid。当然关机除外。。
  17. 这时可以成做pid为这个无终端的会话组长。
  18. 注意这个函数需要当前进程不是父进程,或者说不是会话组长。
  19. 在这里当然不是,因为父进程已经被kill
  20. 换句话来说就是 : 调用进程必须是非当前进程组组长,调用后,产生一个新的会话期,且该会话期中只有一个进程组,且该进程组组长为调用进程,没有控制终端,新产生的group ID 和 session ID 被设置成调用进程的PID
  21. */
  22. if( !posix_setsid() ){
  23. exit('setsid error.');
  24. }
  25. // 理论上一次fork就可以了
  26. // 但是,二次fork,这里的历史渊源是这样的:在基于system V的系统中,通过再次fork,父进程退出,子进程继续
  27. // 保证形成的daemon进程绝对不会成为会话首进程,不会拥有控制终端。
  28. $pid = pcntl_fork();
  29. if( $pid < 0 ){
  30. exit('fork error');
  31. } else if( $pid > 0 ) {
  32. // 主进程退出
  33. exit;
  34. }
  35. // 子进程继续执行
  36. // 给进程重新起个名字
  37. cli_set_process_title('php master process');
加入我们fork出5个子进程就可以搞定这些任务,那么fork出5个子进程,同时父进程要负责这5个子进程的状态等。
  1. // 由于*NIX好像并没有(如果有,请告知)可以获取父进程fork出所有的子进程的ID们的功能,所以这个需要我们自己来保存
  2. $child_pid = [];
  3. // 父进程安装SIGCHLD信号处理器并分发
  4. pcntl_signal( SIGCHLD, function(){
  5. // 这里注意要使用global将child_pid全局化,不然读到去数组将为空,具体原因可以自己思考下
  6. global $child_pid;
  7. // 如果子进程的数量大于0,也就说如果还有子进程存活未 退出,那么执行回收
  8. $child_pid_num = count( $child_pid );
  9. if( $child_pid_num > 0 ){
  10. // 循环子进程数组
  11. foreach( $child_pid as $pid_key => $pid_item ){
  12. $wait_result = pcntl_waitpid( $pid_item, $status, WNOHANG );
  13. // 如果子进程被成功回收了,那么一定要将其进程ID从child_pid中移除掉
  14. /*
  15. 可能有朋友疑惑为什么要判断$wait_result == $pid_ite,也不知道这时候程序运行到哪里了,
  16. 大家是否还记得第四章php多进程初探---信号中提到循环while等待子进程被回收,出现20个0,第21个输出子进程号,所以这里foreach判断是否等于子进程号,-1 == $wait_result就不用多讲,也提到,子进程找不到了
  17. */
  18. if( $wait_result == $pid_item || -1 == $wait_result ){
  19. unset( $child_pid[ $pid_key ] );
  20. }
  21. }
  22. }
  23. } );
  24. // fork出5个子进程出来,并给每个子进程重命名
  25. for( $i = 1; $i <= 5; $i++ ){
  26. $_pid = pcntl_fork();
  27. if( $_pid < 0 ){
  28. exit();
  29. } else if( 0 == $_pid ) {
  30. // 重命名子进程
  31. cli_set_process_title('php worker process');
  32. // 啦啦啦啦啦啦啦啦啦啦,请在此处编写你的业务代码
  33. // do something ...
  34. // 啦啦啦啦啦啦啦啦啦啦,请在此处编写你的业务代码
  35. // 子进程退出执行,一定要exit,不然就不会fork出5个而是多于5个任务进程了
  36. exit();
  37. } else if( $_pid > 0 ) {
  38. // 将fork出的任务进程的进程ID保存到数组中
  39. $child_pid[] = $_pid;
  40. }
  41. }
  42. // 主进程继续循环不断派遣信号
  43. while( true ){
  44. pcntl_signal_dispatch();
  45. // 每派遣一次休眠一秒钟
  46. sleep( 1 );
  47. }