guanhui07/blog

再看 PHP进程控制

Opened this issue · 0 comments

PHP进程控制
PHP 中的进程控制是指对进程(程序的执行单位)的控制,包括创建进程、暂停进程、终止进程等。

在 PHP 中,可以使用 pcntl 扩展来实现进程控制,例如:

<?php

// 创建进程
$pid = pcntl_fork();

// 暂停进程
pcntl_waitpid($pid, $status);

// 终止进程
posix_kill($pid, SIGKILL);
<?php
declare(strict_types=1);


$pid = pcntl_fork();
if ($pid > 0) {
    // 下面这个函数可以更改php进程的名称
    cli_set_process_title('php father process');

    // 返回值保存在$wait_result中
    // $pid参数表示 子进程的进程ID
    // 子进程状态则保存在了参数$status中
    // 将第三个option参数设置为常量WNOHANG,则可以避免主进程阻塞挂起,此处父进程将立即返回继续往下执行剩下的代码
    $waitResult = pcntl_waitpid($pid, $status, WNOHANG);

    //$wait_result大于0代表子进程已退出,返回的是子进程的pid,非阻塞时0代表没取到退出子进程,为什么会没有取到子进程,因为当时子进程没有退出,在休眠sleep

    var_dump($waitResult);
    var_dump($status);
    echo "不阻塞,运行到这里" . PHP_EOL;

    // 让主进程休息60秒钟
    sleep(60);

} else if (0 == $pid) {
    cli_set_process_title('php child process');
    // 让子进程休息10秒钟,但是进程结束后,父进程不对子进程做任何处理工作,这样这个子进程就会变成僵尸进程
    sleep(10);
} else {
    exit('fork error.' . PHP_EOL);
}

上面的代码演示了如何使用 pcntl 扩展来控制进程。需要注意的是,在使用 pcntl 扩展时,
需要在 PHP.ini 中开启相应的扩展。
使用进程控制的时候,还需要注意以下几点:
创建的进程数量不能太多,否则可能会导致系统资源不足。
进程控制是操作系统级别的操作,所以不能在虚拟主机或共享主机环境中使用。
在使用进程控制时,需要注意资源的管理,避免出现冲突或死锁的情况。

线程、进程、协程的区别?
这里我就拿php-fpm举例,每个php-fpm进程都是它master进程的子进程。
因为php-fpm没有多线程,虽然我们一直只讲进程,但是每个php-fpm进程
都是有一个它自己的主线程的。
多线程可以共享内存数据,但是多个进程
之间没法直接共享数据,得通过进程间互相通信(消息队列、socket等方式)
才能交换数据。进程的开销比线程大。
至于协程,用户态轻量级线程,它是用户主动对于cpu的中断
调度,这样上下文的切换肯定要比线程效率高,所以现在golang这么流行,swoole也有协程

初识fork pcntl_fork
php 中的 fork 是一个操作系统级别的概念,它可以让你在代码中创建一个子进程,
从而实现多线程编程。使用 fork 可以实现许多功能,例如后台任务处理、数据处理等。

在 php 中,可以使用 pcntl_fork() 函数来实现 fork 操作,例如:

<?php

// 创建子进程
$pid = pcntl_fork();
if ($pid == -1) {
    // 创建失败
    exit("Failed to fork");
} elseif ($pid) {
    // 父进程
    // ...
} else {
    // 子进程
    // ...
}

在上面的代码中,当调用 pcntl_fork() 函数时,程序会创建一个子进程,
并在父进程和子进程中执行不同的代码。

使用 fork 的时候,需要注意以下几点:
fork 后的子进程会继承父进程的内存状态,所以在处理数据时要注意避免冲突。
子进程中的变量不会影响到父进程中的变量,所以在实现多线程编程时,需要自己管理数据共享。
fork 的操作系统级别,所以不能在虚拟主机或共享主机环境中使用。

僵尸进程与进程回收
php 中的僵尸进程是指在执行完成后,操作系统还未回收的进程。
这种进程通常不会对系统产生什么影响,但是如果僵尸进程数量过多
,会导致系统资源的浪费,从而影响系统的性能。

在 PHP 中,可以使用 pcntl_waitpid() 函数来回收僵尸进程。

<?php

// 创建进程
$pid = pcntl_fork();
if ($pid == -1) {
    // 创建失败
    exit("Failed to fork");
} elseif ($pid) {
    // 父进程
    // 等待子进程结束
    pcntl_waitpid($pid, $status);
    // 回收子进程

pcntl_waitpid() 函数可以用来回收子进程,在回收过程中,父进程会
暂停执行,直到子进程结束后才会继续执行。这样可以保证在子进程结束后,
能够及时回收它占用的系统资源。

php僵尸进程是指在php中,一个子进程在结束执行后没有被正确地
回收,导致其成为一个“僵尸进程”。这种情况通常发生在父进程意外终止,导致
它无法调用 wait() 来回收子进程的状态。僵尸进程会占用系统资源,并且可能导致系统的性能下降。

为了避免这种情况的发生,php提供了 pcntl_waitpid() 函数
来进行进程回收。这个函数会挂起父进程的执行,直到指定的子进程结束,
然后进行回收,从而避免僵尸进程的产生。

应用程序可以使用 pcntl_waitpid() 函数来回收子进程,或者可以使
用 pcntl_signal() 函数来设置一个信号处理程序,在子进程结束时自
动进行进程回收。例如:

// 在父进程中设置信号处理程序
pcntl_signal(SIGCHLD, "sig_handler");

// 在子进程中执行代码
$pid = pcntl_fork();
if ($pid == 0) {
  // 子进程代码
} else {
  // 父进程代码
}

// 信号处理程序
function sig_handler($signo) {
  switch ($signo) {
    case SIGCHLD:
      // 回收子进程
      pcntl_waitpid(-1, $status);
      break;
  }
}
$pid = pcntl_fork();
if( 0 > $pid ){
  exit('fork error.'.PHP_EOL);
} else if( 0 < $pid ) {
  // 在父进程中
  // 给父进程安装一个SIGCHLD信号处理器
  pcntl_signal( SIGCHLD, function() use( $pid ) {
    echo "收到子进程退出".PHP_EOL;
	pcntl_waitpid( $pid, $status, WNOHANG );
  } );
  cli_set_process_title('php father process');
  // 父进程不断while循环,去反复执行pcntl_waitpid(),从而试图解决已经退出的子进程
  while( true ){
    sleep( 1 );
	// 注释掉原来老掉牙的代码,转而使用pcntl_signal_dispatch()
    //pcntl_waitpid( $pid, $status, WNOHANG );
	pcntl_signal_dispatch();
  }
} else if( 0 == $pid ) {
  // 在子进程中
  // 子进程休眠20秒钟后直接退出
  cli_set_process_title('php child process');
  sleep( 20 );
  exit;
}

孤儿进程 是父进程先挂了 init回收
僵尸进程 是 子进程挂了 但文件描述符 等没回收 waitpid

常见服务器进程模型
多进程 master process

PHP 主要使用两种服务器进程模型:每个请求创建一个新的进程(CGI)
和长时间运行的进程(FastCGI)。

在 CGI 模型中,每次 HTTP 请求都会创建一个新的 PHP 进程来处理该请求。
这种方式实现简单,但效率较低,因为创建进程和销毁进程都需要耗费一定的时间和资源。

FastCGI 模型使用一个长时间运行的 PHP 进程池来处理请求,
每个请求都由池中的一个进程处理。这种方式效率更高,因为
创建和销毁进程的时间和资源消耗可以忽略不计。

除了 CGI 和 FastCGI 两种模型,PHP 还支持其他一些服务器进程模型,
如 Apache 模块模型(mod_php)和命令行模型(CLI)。


常见的服务器进程模型包括多线程模型、多进程模型和事件驱动模型。

多线程模型中,一个单独的进程包含多个执行线程,所有线程共享进程的资源。
这种模型可以有效地处理多个并发请求,因为线程之间可以共享内存空间,
通信和同步更容易实现。但是,如果一个线程出现问题并崩溃,整个进程也会崩溃。

多进程模型中,服务器启动多个进程来处理请求。每个进程都有自己的内存空间,
互相独立,因此如果一个进程崩溃了,不会对其他进程造成影响。但是,
由于每个进程都有自己的内存空间,通信和同步会比较困难。

事件驱动模型中,服务器只启动一个进程,但是该进程会创建许多线程来处理请求。
每个请求都会被封装成一个事件,然后放入事件队列中。
一个事件循环(event loop)不断地从事件队列中取出事件,
并将事件分发给相应的处理器来处理。这种模型可以很好地

处理多个并发请求,因为请求都是通过事件的形式进行处理的,
所以通信和同步也比较容易实现。此外,由于只有一个进程,所以
可以避免多进程模型中出现的资源浪费问题。但是,
如果事件队列堆积了大量的事件,
可能会导致服务器负载过重,影响服务器的性能。