A Preliminary Study of PHP Multiprocesses-Signals

[Original Address:https://blog.ti-node.com/blog …]

The last full discussion on pcntl_wait () and pcntl_waitpid () was just to solve the problem of zombie process, but at the end there still seemed to be some left-over problems, and the lack of mouth at the end of the last article also gave the solution: signal.

Signal is a software interrupt and a very typical asynchronous event processing method. InAt the beginning of chaos when NIX system was born, the definition of signal was quite chaotic, and the most important thing was unreliable, which was a very serious problem. Therefore, in the POSIX standard that followed, the signal was standardized, and at the same time, each distribution versionNIX also provides a large number of reliable signals. Each signal has its own name, such as SIGTERM, SIGHUP, SIGCHLD, etc. In *NIX, these signals are essentially shaped numbers (if you are in a mood, you can take a look at the header files of the signal.h series).

There are many ways to generate signals, the following are common ones:

  • Pressing certain key combinations on the keyboard, such as Ctrl+C or Ctrl+D, will generate SIGINT signals.
  • Use posix kill call to send a specified signal to a process.
  • In the case of a remote ssh terminal, if you execute a blocking script on the server and shut down the terminal during the blocking process, a SIGHUP signal may be generated.
  • The hardware will also generate signals, such as OOM or the case of dividing by 0. The hardware will also send specific signals to the process.

After receiving the signal, the process can have the following three responses:

  • Directly ignore, do not make any response. Is commonly known as no bird at all. However, there are two kinds of signals that will never be ignored. One is SIGSTOP and the other is SIGKILL, because these two processes provide the kernel with the last reliable way to end the process.
  • Captures signals and makes corresponding responses. The specific response can be customized by the user through the program.
  • System default response. After most processes encounter signals, if the user does not have a customized response, the system default response will be adopted, and most of the system default response is to terminate the process.

People use words to express, that is to say, if you are a process and you are working, suddenly the horn of the construction team shouts at you: “dinner!” , so you put down your work to eat. You were working when suddenly the horn of the construction team shouted at you, “wages have been paid!” So you put down your work and get paid. You were working when suddenly the horn of the construction team shouted at you: “someone is looking for you!” , so you put down your work to see who is looking for you and what things. Of course, you are very capricious, that is completely can not bird horn shout what content, that is, ignore the signal. It can also be more capricious. When the horn shouts “eat” at you, you will not go to eat, you will go to sleep, all of which can be done by you. In the process of working, you will never stop waiting for a signal just because you have to wait for a signal. Instead, the signal may come anytime and anywhere, and you only need to make a corresponding response at this time. Therefore, the signal is a software interrupt and an asynchronous way of handling events.

Returning to the problem mentioned above, the parent process called pcntl_waitpid () before the child process ended, resulting in the child process still becoming a zombie process after the end. In fact, calling pcntl_waitpid () in the while loop of the parent process is a solution, and the approximate code is as follows:

$pid = pcntl_fork();
if( 0 > $pid ){
  exit('fork error.'.PHP_EOL);
} else if( 0 < $pid ) {
  // 在父进程中
  cli_set_process_title('php father process');
  // 父进程不断while循环,去反复执行pcntl_waitpid(),从而试图解决已经退出的子进程
  while( true ){
    sleep( 1 );
    pcntl_waitpid( $pid, &$status, WNOHANG );
  }
} else if( 0 == $pid ) {
  // 在子进程中
  // 子进程休眠3秒钟后直接退出
  cli_set_process_title('php child process');
  sleep( 20 );
  exit;
}

The following figure shows the results of the operation:

解析一下这个结果,我先后三次执行了ps -aux | grep php去查看这两个php进程。
  • First time: child process is sleeping, parent process is still in loop.
  • The second time: the child process has exited and the parent process is still in the loop, but the code has not been executed to pcntl_waitpid (), so the child process becomes a zombie process in the gap between the child process exiting and the parent process executing recycling.
  • The third time: at this time, the parent process has already executed pcntl_waitpid (), recycling the child processes that have exited, and releasing pid and other resources.

However, this code has a defect. In fact, the main process is still recycling the subprocess while the subprocess has already exited. This is a very strange thing. It does not conform to the mainstream values of socialism. It is neither low carbon nor energy-saving, nor elegant nor attractive. Therefore, we should consider a better way to achieve it. So, the long-awaited signal finally came out in outline.

Now let’s consider why signals can solve the problem of “no low carbon, no energy saving, no elegant code, no good looking”. When the child process exits, it will send a signal, called SIGCHLD, to the parent process. once the parent process receives this signal, it can make corresponding recycling actions, i.e. pcntl_waitpid (), thus eliminating the zombie process. moreover, it also shows that our code is elegant, beautiful, energy-saving and environmentally friendly.

To sort out the process, it is transparent for people for the child process to send SIGCHLD signals to the parent process, which means we don’t need to care. However, we need to install a processor for the parent process to respond to SIGCHLD signals. in addition, we also need to make these signal processors run. it is an embarrassing thing to install and not run. Then, the function used to install the signal processor in php is pcntl_signal (), and the function used to run the signal processor is pcntl_signal_dispatch ().

  • Pcntl_signal (), install a signal processor, specifically pcntl _ signal (int $ signo, callback $ handler [,bool $ restart _ syscalls = true]), parameter signo is the signal, callback is the code segment that responds to the signal, and returns bool value.
  • Pcntl_signal_dispatch (), calls the processor installed by pcntl_signal () for each waiting signal, with void as the parameter, and returns bool value.

The following two newly introduced functions are combined to solve the ugly code upstairs:

$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 ) {
  // 在子进程中
  // 子进程休眠3秒钟后直接退出
  cli_set_process_title('php child process');
  sleep( 20 );
  exit;
}

The operation results are as follows:


[Original Address:https://blog.ti-node.com/blog …]