跳至主要內容

孤儿进程,僵尸进程,守护进程 php 代码演示

安格Linuxlinux大约 6 分钟...

孤儿进程: 父进程退出,子进程被 init(pid=1) 进程接管即为孤儿进程;
僵尸进程: 父进程仍在运行,子进程已退出但是未被父进程调用 wait / waitpid 完成状态收集而变成僵尸进程;
守护进程: 脱离终端的后台服务进程,是个特殊的孤儿进程;

孤儿进程

// 孤儿进程
function demoOrphan()
{
    $pid = pcntl_fork();
    if (0 == $pid) {
        printf("[子进程 %d] 运行中...\n", posix_getpid());
        for ($i = 1; $i <= 10; $i++) {
            $ppid = posix_getppid();
            if ($ppid == 1) {
                printf("\n[子进程(孤儿进程) %d] 运行中,父进程是: %d", posix_getpid(), $ppid);
            } else {
                printf("[子进程 %d] 运行中,父进程是: %d\n", posix_getpid(), $ppid);
            }
            sleep(1);
        }
    } else if ($pid > 0) {
        printf("[父进程 %d] 运行中...\n", posix_getpid());
        sleep(3);
        printf("[父进程 %d] 现在退出。子进程将变成孤儿进程并由 init(pid=1) 接管\n", posix_getpid());
    }
}

demoOrphan();
demoOrphan
demoOrphan

从上图可以看出,父进程 2285 在 3 秒后退出,子进程 2286 继续执行并变成了孤儿进程(ppid=1)。

僵尸进程

// 僵尸进程
function demoZombie()
{
    // START:
    $i_pid = pcntl_fork();
    if (0 == $i_pid) {
        // 子进程5s后退出,变成僵尸进程
        for ($i = 0; $i < 5; $i++) {
            printf("[子进程 %d] 运行中...\n", posix_getpid());
            sleep(1);
        }
        printf("[子进程 %d] 执行结束退出,变成僵尸进程\n", posix_getpid());
    } else if ($i_pid > 0) {
        // 父进程休眠15s后退出.
        printf("[父进程 %d] 开始长时间sleep\n", posix_getpid());
        sleep(15);
        printf("[父进程 %d] 终于睡醒了\n", posix_getpid());
        // goto START; // 取消这行注释可以逐渐创建多个僵尸进程
    }
}

demoZombie();
demoZombie
demoZombie

从上图可以看出,父进程 2340 在启动后进入长时间 sleep ,子进程 2341 执行 5 秒后退出并变成了僵尸进程(可以看到进程 2341 的 S 列变成了Z)。

正常父子进程

// 正常的父子进程
function demoNormal()
{
    $i_pid = pcntl_fork();
    if (0 == $i_pid) {
        // 在子进程中
        for ($i = 1; $i <= 5; $i++) {
            printf("子进程PID: %d 倒计时 %ds 后退出\n", posix_getpid(), $i);
            sleep(1);
        }
    } else if ($i_pid > 0) {
        // 处理子进程退出, 防止变成僵尸进程
        $i_ret = pcntl_wait($status);
        echo $i_ret . ' : ' . $status . PHP_EOL;
        // 保持父进程不退出
        while (true) {
            echo "父进程持续运行中...", PHP_EOL;
            sleep(1);
        }
    }
}

demoNormal();
demoNormal
demoNormal

从上图可以看出,子进程 2361 在 5 秒后退出并由父进程调用了 pcntl_wait 正常结束了进程。

守护进程

/**
 * 将进程变成守护进程
 * 再需要守护的时候调用该函数, 该函数后面的代码即运行在 daemon 进程中
 * @return void
 */
function Daemonize()
{
    $pid = pcntl_fork();
    if ($pid > 0) { // 1)在父进程中执行fork并exit推出
        exit();
    } elseif ($pid == 0) {
        if (posix_setsid() < 0) { // 2)在子进程中调用setsid函数创建新的会话
            exit();
        }
        chdir('/'); // 3)在子进程中调用chdir函数,让根目录 ” / ” 成为子进程的工作目录
        umask(0); // 4)在子进程中调用umask函数,设置进程的umask为0
        echo "deamon create success, pid = " . posix_getpid(), PHP_EOL;
        // 5)在子进程中关闭任何不需要的文件描述符
        fclose(STDIN);
        fclose(STDOUT);
        fclose(STDERR);
    }
}

// 守护进程
function demoDaemon()
{
    $cwd = getcwd();
    Daemonize();

    // 下面的代码即运行在守护进程中
    cli_set_process_title("[php Worker Process] " . posix_getpid()); //修改子进程的名字

    function logLine($str)
    {
        return sprintf("[%s] [pid %d] %s\n", date('Y-m-d H:i:s'), posix_getpid(), $str);
    }

    // 由于 daemonize 时关掉了标准输入输出, 这里把输出写到文件中
    // 准备日志文件
    $fLog = $cwd . '/log.log';
    $fdLog = fopen($fLog, "a");

    // 开始具体业务
    $i = 0;
    while ($i++ < 10) {
        fwrite($fdLog, logLine("处理任务中..."));
        sleep(1);
    } // 具体业务
    fwrite($fdLog, logLine("处理任务完成,退出"));
    fclose($fdLog);
}

demoDaemon();
demoDaemon
demoDaemon

从上图可以看出,守护进程 2410 成功创建并脱离了终端在后台运行,守护进程的名称自定义设置成功: [php Worker Process] 2410, 10 秒后进程执行完成退出。

附:

posix 命令

// 需要安装posix扩展
posix_getpid(); // 获取进程ID
posix_getppid(); // 获取父进程ID
posix_getpgid(posix_getppid()); // 获取进程组ID
posix_getpgrp()); // 同上
posix_getsid(posix_getpid())); // 获取会话ID
posix_getuid(); // 获取当前登录用户UID
posix_getgid(); // 获取当前登录用户组UID
posix_geteuid(); // 获取当前有效用户UID
posix_getguid(); // 获取当前有效用户组UID
posix_kill(); // 发送信号

pcntl 命令

//创建一个计时器,在指定的秒数后向进程发送一个SIGALRM信号。每次对 pcntl_alarm()的调用都会取消之前设置的alarm信号。如果seconds设置为0,将不会创建alarm信号。
pcntl_alarm(int $seconds);
//在当前进程当前位置产生子进程,子进程会复制父进程的代码段和数据段(Copy on write 写时复制,当子进程要修改内存空间时,操作系统会分配新的内存给子进程),ELF文件的结构,如果父进程先退出,子进程变成孤儿进程,被pid=1进程接管
pcntl_fork();
//安装一个信号处理器
pcntl_signal(int $signo, callback $handler);
//调用等待信号的处理器,触发全部未执行的信号回调
pcntl_signal_dispatch()
//设置或检索阻塞信号
pcntl_sigprocmask(int $how, array $set[, array &$oldset])
//等待或返回fork的子进程状态,wait函数挂起当前进程的执行直到一个子进程退出或接收到一个信号要求中断当前进程或调用一个信号处理函数。用此函数时已经退出(俗称僵尸进程),此函数立刻返回。子进程使用的所有系统资源将被释放。
pcntl_wait($status)
//加个WNOHANG参数,不挂起父进程,如果没有子进程退出返回0,如果有子进程退出返回子进程pid,如果返回-1表示父进程已经没有子进程
pcntl_wait($status, WNOHANG)
//基本同pcntl_wait,waitpid可以指定子进程id
pcntl_waitpid ($pid ,$status)
pcntl_waitpid ($pid ,$status, WNOHANG)
//检查状态代码是否代表一个正常的退出。参数 status 是提供给成功调用 pcntl_waitpid() 时的状态参数。
pcntl_wifexited($status)
//返回一个中断的子进程的返回代码  当php exit(10)时,这个函数返回10,这个函数仅在函数pcntl_wifexited()返回 TRUE.时有效
pcntl_wexitstatus($status)
//检查子进程状态码是否代表由于某个信号而中断。参数 status 是提供给成功调用 pcntl_waitpid() 时的状态参数。
pcntl_wifsignaled($status)
//返回导致子进程中断的信号
pcntl_wtermsig($status)
//检查子进程当前是否已经停止,此函数只有作用于pcntl_wait使用了WUNTRACED作为 option的时候
pcntl_wifstopped($status)
//返回导致子进程停止的信号
pcntl_wstopsig($status)
//检索由最后一个失败的pcntl函数设置的错误数
pcntl_errno()
pcntl_get_last_error()
//检索与给定errno关联的系统错误消息
pcntl_strerror(pcntl_errno())

参考:

PHP 多进程开发[快问快答系列]open in new window

上次编辑于:
你认为这篇文章怎么样?
  • 0
  • 0
  • 0
  • 0
  • 0
  • 0
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v3.1.3