php multi process summary

Multi-process --fork

Scenario: In daily tasks, sometimes you need to perform some log analysis, queue processing and other tasks through the php script. When the amount of data is relatively large, you can use multiple processes to process.

Preparation: php multi-process requires pcntl, posix extended support, can be viewed through php-m, if not installed, you need to recompile php, plus parameters--enable-pcntl,posix一般默认会有。

note:

    Multi-process implementation can only be in cli mode, in the web server environment, there will be unexpected results, I test报错:Call to undefined function: pcntl_fork()

   An error pcntl_fork causing "errno = 32 Broken pipe" # 474, see https://github.com/phpredis/phpredis/issues/474

        Pay attention to two points: If you create a child process in a loop, then the child process must exit at the end to prevent the child process from entering the loop.
                      The open connection in the child process cannot be copied, and the main process is used. Multiple case mode is required.

pcntl_fork:

  One call returns twice, the child process pid is returned in the parent process, 0 is returned in the child process, and -1 is returned on error.

 pcntl_wait ( int &$status [, int $options ] ):

    Block the current process until any child process exits or receives a signal to end the current process. Note that it is a signal to end the current process. The SIGCHLD sent by the child process does not count. Use $ status to return the status code of the child process, and you can specify the second parameter to indicate whether to call in blocking state

        Called in blocking mode, the return value of the function is the pid of the child process, if there is no child process, the return value is -1;

        Called in a non-blocking manner, the function can also return 0 when there are child processes running but not ending.

pcntl_waitpid ( int $pid , int &$status [, int $options ] )
         The function is the same as pcntl_wait, the difference is that waitpid is a child process waiting for the specified pid. When pid is -1, pcntl_waitpid is the same as pcntl_wait. The status information of the child process is stored in $ status in the two functions pcntl_wait and pcntl_waitpid.
 
Check if it is cli mode
/ ** Make sure this function can only be run in SHELL * / 
if (substr (php_sapi_name (), 0, 3)! == 'cli') { 
  die ("cli mode only"); 
}

 

The SHELL script implements multi-process (this is how Qbus multi-process is implemented, except that nohup and & are used as daemons):
Copy code
#!/bin/bash
 
for((i=1;i<=8;i++))
do    
    /usr/local/bin/php multiprocessTest.php &
done
 
wait
Copy code

The above shell program lists a very simple multi-process program, using a for loop, to achieve 8 processes concurrently to run multiprocessTest.php this program. The last wait statement can also make the main process wait for all processes to execute before executing down.

This program is no problem, and many existing codes are also implemented in this way, but the number of concurrent programs of this program is uncontrollable, that is, we cannot schedule the switch of each process according to the number of cores of the machine.

If our machine has 8 cores or more, the above program is no problem, all cores can be fully utilized, and there is no competition for resources between each other.

But what will happen if our machine does not have 8 cores, the number of processes running at the same time is more than the number of cores, then the system will have the problem of process allocation and scheduling, and the contention for resources will also follow accordingly. A process cannot guarantee independence Continuous execution, all processes will follow the system's scheduling, so that more uncertain factors will appear.

An example of always running a fixed number of child processes
Copy code
<? php 

// Maximum number of child processes 
$ maxChildPro = 8; 

// Current number of child processes 
$ curChildPro = 0; 

// This function will be triggered when the child process exits, current number of child processes -1 
function sig_handler ($ sig) 
{ 
    global $ curChildPro; 
    switch ($ sig) { 
        case SIGCHLD: 
            echo 'SIGCHLD', PHP_EOL; 
            $ curChildPro--; 
            break; 
    } 
} 

// Used with pcntl_signal, to put it simply, it is for the system to generate time clouds, Let the signal capture function capture the semaphore 
declare (ticks = 1); 

// The function called when the registered child process exits. SIGCHLD: When a process terminates or stops, it sends a SIGCHLD signal to its parent process. 
pcntl_signal (SIGCHLD, "sig_handler"); 

while (true) { 
    $ curChildPro ++; 
    $ pid = pcntl_fork (); 
    if ($ pid) { 
// The parent process runs code, when the upper limit is reached, the parent process blocks and waits for any child process to exit while the loop continues
        if ($ curChildPro> = $ maxChildPro) { 
            pcntl_wait ($ status); 
        } 
    } else { 
// Child process running code 
        $ s = rand (2, 6); 
        sleep ($ s); 
        echo "child sleep $ s second quit ", PHP_EOL; 
        exit; 
    } 
}
Copy code

 

An example of using the waitpid function to wait for all child processes to exit to prevent zombie processes
Copy code
<? php 

$ childs = array (); 

// Fork 10 child processes 
for ($ i = 0; $ i <10; $ i ++) { 
    $ pid = pcntl_fork (); 
    if ($ pid == -1) 
        die (' Could not fork '); 

    if ($ pid) { 
        echo "parent \ n"; 
        $ childs [] = $ pid; 
    } else { 
// Sleep $ i + 1 (s). The child process can get the $ i parameter 
        sleep ( $ i + 1); 

// The child process needs to exit to prevent the child process from entering the for loop 
        exit (); 
    } 
} 

while (count ($ childs)> 0) { 
    foreach ($ childs as $ key => $ pid) { 
        $ res = pcntl_waitpid ($ pid, $ status, WNOHANG); 

//-1 means error, greater than 0 means the child process has exited, the pid of the child process is returned, and 0 means that the child process has not been exited 
        if not blocked if ( $ res == -1 || $ res> 0) 
            unset ( $ childs [$ key]); 
    }

    sleep(1);
}
Copy code

 

A practical example, php implements concurrent log copy
Copy code
<?php
function _fetchLog()
{
    $password        = $this->_getPassword();
    $online_log_path = NginxConf::getArchiveDir($this->_stat_day);
    $task_log_path   = QFrameConfig::getConfig('LOG_PATH');
    $children        = array();
    $success         = true;
    foreach($this->_server_list as $host => $value)
    {
        $local_dir = $this->_prepareLocalDir($host);
        $task_log  = "$task_log_path/fetch_log.$host";
        $cmd = "sshpass -p $password rsync -av -e 'ssh -o StrictHostKeyChecking=no' $host:$online_log_path/* $local_dir >> $task_log 2>&1";
        $pid = pcntl_fork();
        if(-1 === $pid)
        {
            LogSvc::log('stat_pv_by_citycode_error', 'could not fork');
            exit('could not fork');
        }
        else if(0 === $pid)
        {
            system($cmd, $return_value);
            if(0 !== $return_value)
            {
                LogSvc::log('stat_pv_by_citycode_error', "rsync $host error");
            }
            exit($return_value);
        }
        else
        {
            $children[$pid] = 1;
        }
    }
    while(!empty($children))
    {
        $pid = pcntl_waitpid(-1, $status, WNOHANG);
        if(0 === $pid)
        {
            sleep(1);
        }
        else
        {
            if(0 !== pcntl_wexitstatus($status))
            {
                $success = false;
            }
            unset($children[$pid]);
        }
    }
    return $success;
} 
Copy code

 
Multi-process-signal
 
Only one signal of the same type can be stored, and the excess is automatically discarded
You can send a value less than 0 to send to all child processes, including yourself, which is actually a group of processes.
 
PCNTL uses ticks as a signal handle callback mechanism to minimize the load on handling asynchronous events. What are ticks? Tick ​​is an event that occurs every time the interpreter executes N low-level statements in a code segment. This code segment needs to be specified by declare.
 
    pcntl_signal (int $ signo, callback $ handler [, bool $ restart_syscalls]) // register a handler for a SIG
      posix_kill(posix_getpid(), SIGHUP); 为自己生成SIGHUP信号
       declare(ticks = 1); //php < 5.3
      pcntl_signal_dispatch ( void )  
        Call each  processor that waits for the signal to be installed via pcntl_signal () . Explain: pcntl_signal() function just registered signal and its processing method, the real received signal and call its treatment method is pcntl_signal_dispatch() a function must be called in the cycle, in order to detect whether a new signal wait dispatching.
      pcntl_signal_dispatch()
      This function is only supported by PHP 5.3 and above. If your PHP version is greater than 5.3, it is recommended to use this method to call the signal processor. The version below 5.3 needs to add a sentence before registering the signal: It declare(ticks = 1); means that every time a low-level instruction is executed, the signal is checked once, and if the registered signal is detected, its signal processor is called.
     pcntl_alarm ( int $seconds )
        Set a counter to send SIGALRM signal after $ seconds
                        
                        
5. Send signal:
 
    posix_kill (): Send a signal to the process. 
    SIGINT: Via keyboard CTRL + C.
    SIGTERM: sometimes unresponsive process will execute the kill [PID] command, did not add any other parameters, the program will receive a SIGTERM signal.        
           When the program receives the above two signals, the execution will end by default, and the default behavior can be changed by registering the signal.
 
An example of registering a signal processor draws the conclusion:
The sleep function will be woken up by the signal, no longer sleep, and return the number of seconds remaining when waking up
For the same kind of signal, only one can be stored, and the excess is automatically discarded. After testing, it is found that after multiple CTRL + C, the process will first wake up sleep, and then wake up usleep. When pcntl_signal_dispatch is executed, multiple "SIGINT" will be output at a time. Store one is not the only place.
Copy code
<? php 

// Define a processor that only outputs one line of information after receiving the SIGINT signal 
function signalHandler ($ signal) 
{ 
    if ($ signal == SIGINT) { 
        echo 'SIGINT', PHP_EOL; 
    } 
} 

// Signal registration: when When the SIGINT signal is received, call the signalHandler () function 
pcntl_signal (SIGINT, 'signalHandler'); 

/ ** 
 * PHP <5.3 Use 
 * Used with pcntl_signal, which means that the signal is checked every time a low-level instruction is executed. If a registered signal is detected Signal, call its signal processor. 
 * / 
if (! function_exists ("pcntl_signal_dispatch")) { 
    declare (ticks = 1); 
} 

while (true) { 
    $ s = sleep (10); 
    echo $ s, PHP_EOL; // The signal will wake up sleep and return the rest Seconds. 

// do something 
    for ($ i = 0; $ i <5; $ i ++) { 
        echo $ i. PHP_EOL;
        usleep (100000); 
    }

    / ** 
     * PHP> = 5.3 
     * Call the installed signal processor 
     * Must be called in the loop, in order to detect whether there is a new signal waiting for dispatching. 
     * / 
    if (! function_exists ("pcntl_signal_dispatch")) { 
        pcntl_signal_dispatch (); 
    } 

}
Copy code

 

An example of sending a signal every 5s, through pcntl_alarm
Copy code
<?php

declare(ticks = 1);

function signal_handler($signal) {
    print "Caught SIGALRM\n";
    pcntl_alarm(5);
}

pcntl_signal(SIGALRM, "signal_handler", true);
pcntl_alarm(5);

for(;;) {
}
Copy code

 

An example of killing a process by sending a signal. The signal can be sent to yourself or to other processes.
Copy code
<? php 

/ ** 
 * The parent process waits for the child process to exit through pcntl_wait 
 * The child process kills itself through the signal, or it can send a kil signal in the parent process to end the child process 
 * / 

// Generate the child process 
$ pid = pcntl_fork (); 
if ($ pid == -1) { 
    die ('could not fork'); 
} else { 
    if ($ pid) { 
        $ status = 0; 
// Block the parent process until the end of the child process, not suitable for long-running Script. 
        // You can use pcntl_wait ($ status, WNOHANG) to implement non-blocking 
        pcntl_wait ($ status); 
        exit; 
    } else { 
// End the current child process to prevent the zombie process from being generated 
        if (function_exists ("posix_kill")) { 
            posix_kill (getmypid (), SIGTERM); 
        } else { 
            system ('kill -9'. getmypid ()); 
        } 
        exit; 
    } 
}
Copy code

 
Multi-process-zombie process
 
When the child process exits before the parent process, but the parent process is still running and may not exit for a long time, the child process becomes a zombie process. Then the kernel will find the PPID of the zombie process and send a SIGCHLD signal to the PPID. If the parent process does not pass the pcntl_wait or pcntl_waitpid function, and does not send an exit signal to the child process through posix_kill, then the child process is completely zombie.
                Z (zombie) status when ps aux is viewed.
                Generally, you need to recycle the child process before the end of the parent process. The pcntl_wait () function will suspend the parent process until a child process exits.
                If the parent process hangs first, the child process will be taken over by process 1, and when the child process ends, process 1 will be automatically recovered. So another way to shut down zombie processes is to shut down their parent process.
                How the child process knows that the parent process exits:
                                1. When the parent process exits, there will be an INIT process to adopt the child process. The process number of this INIT process is 1, so the child process can obtain the pid of the current parent process by using getppid (). If it returns 1, it indicates that the parent process has become the INIT process, then the original process has been launched.
 
                                2. Use the kill function, posix_kill in php, to send an empty signal (kill (pid, 0)) to the original parent process. Use this method to check the existence of a process without actually sending a signal. Therefore, if this function returns -1, the parent process has exited.
 
                Zombie process:
                                In the UNIX system, a process is ended, but his parent process does not wait (call wait / waitpid) him, then he will become a zombie process.
                        The zombie process is a long-dead process, but it still occupies a slot in the processes table.
                        If the zombie process is not recovered in time, it will occupy a process entry in the system. If there are too many such zombie processes, there will be no available process entry in the system, so no other programs can be run.
                                Before any process exits (exit exit), it will become a zombie process. Used to save information such as the state of the process. why?
                                The end of the child process and the running of the parent process are an asynchronous process, that is, the parent process can never predict when the child process will end. Then, will the state information at the end of the child process be lost because the parent process is too busy to wait for the child process, or if it does not know when the child process will end? will not. Because UNIX provides a mechanism to ensure that as long as the parent process wants to know the status information at the end of the child process, it can be obtained. This mechanism is: when the child process has completed its life cycle, it will execute the exit () system call, the kernel releases all resources of the process, including open files, occupied memory, etc. However, certain information is still reserved for it (including the process ID the process ID, exit code exit code, exit status the termination status of the process, running time the amount of CPU time taken by the process, etc.), and these data will be kept until the system Pass it to its parent process until it is released by the parent process through wait / waitpid.
                                
                                Prevent zombie processes
                                                

(1) The parent process waits for the end of the child process through functions such as wait and waitpid, which causes the parent process to hang. It is not suitable for the case where the child process needs to run for a long time (will cause a timeout).      

    Executing the wait () or waitpid () system call, the child process will immediately return its data in the process table to the parent process after termination, and the system will immediately delete the entry point. In this case, there will be no defunct process.

    (2) If the parent process is busy, you can use the signal function to install the handler for SIGCHLD. After the child process ends, the parent process will receive the signal, and you can call wait in the handler to recycle it.

    (3) If the parent process does not care when the child process ends, you can use signal (SIGCLD, SIG_IGN) or signal (SIGCHLD, SIG_IGN) to notify the kernel that you are not interested in the end of the child process, then after the child process ends, the kernel will Recycle and no longer send signals to the parent process

    (4) Fork twice, the parent process forks a child process, and then continues to work, the child process forks a grandchild process and then exits, then the grandchild process is taken over by init, after the grandchild process ends, init will be recycled. However, the recycling of the child process must be done by yourself.

                    
                    View and clear the zombie process: The test found that the ps -ef and aux can not see the zombie process.
                    You need to see the number of zombie processes in the current system in real time through the top command.
                
 
                Use the ps command to view the zombie process:
                
            ps -A -ostat,ppid,pid,cmd | grep -e '^[Zz]'
 
   
           Command annotations: 
  -A parameter lists all processes
  -o custom output field We set the display field to stat (status), ppid (process parent id), pid (process id), cmd (command) these four parameters
The process with status z or Z is a zombie process, so we use grep to grab the stat status as zZ process
 
   
        The results are as follows:
    At this time, you can use kill -HUP 5255 杀掉这个进程。如果再次查看僵尸进程还存在,可以kill -HUP 5253(父进程)。
    如果有多个僵尸进程,可以通过
    ps -A -ostat,ppid,pid,cmd | grep -e '^[Zz]'|awk 'print{$2}'|xargs kill -9
    deal with.



Multi-process-inter-process communication (IPC)
 
Pipes are used to carry communication data between processes. For ease of understanding, the pipeline can be compared to a file, process A writes data to pipeline P, and then process B reads data from pipeline P. The pipeline operation API provided by php is basically the same as the file operation API, except that the pipeline creation uses the posix_mkfifo function, and the read and write operations are the same as the file operation function. Of course, you can directly use the file to simulate the pipeline, but you can't use the characteristics of the pipeline.

Guess you like

Origin www.cnblogs.com/liliuguang/p/12697002.html