In Python, because of the existence of the global interpreter lock Gil, the multithreading in Python does not significantly improve the efficiency of the program (in this case, CPU-intensive), then in the processing of CPU-intensive computing, multi-process model to deal with, The multiprocessing library is available in the Python standard library to support the programming of multi-process models. The process class provided in multiprocessing is used by developers to write child processes that resemble the threading provided by the standard library. The thread class, which also provides the process pool class, reduces the overhead of process creation and destruction to improve reuse (see previous article).
In a multithreaded model, by default (Sub-thread.daemon=false) The main thread waits for a child thread to exit before exiting, and if Sub-thread.setdaemon (True), the main thread does not wait for the child thread to exit directly, In this case, the child thread exits with the main thread's alignment, avoiding the need for a child thread to join in the main thread, waiting for the child thread to finish executing before exiting. Correspondingly, in a multi-process model, the Process class also has the daemon attribute, and it represents a similar meaning to Thread.daemon, and when set sub-process.daemon=true, the child process is required to wait in the main process, Otherwise, the child process exits as the main process exits:
Import Threadingimport timeimport multiprocessingdef fun (args): forIinchRange -): Print args Time.sleep (1) if__name__ = =' __main__ ': Threads= [] forIinchRange4): # t= Threading. Thread (Target=fun, args=(str (i),)) # T.setdaemon (True) T= multiprocessing. Process (Target=fun, args=(str (i),)) T.daemon=True T.start () threads.append (t) forIinchThreads:i.join ()
Running the above code, the main process waits for the child process to exit after execution, ending the entire program. For multithreaded mode, the run effect is similar to a multi-process. Note that the similarity here means that the program is running normally, and when there are human disturbances, such as when the process is started, after the process is killed by kill-9, the situation is different, we know that the multithreaded model is more complex, but also in the same process, killing the main process, All threads will exit with the main process exit, and in the multi-process model, each process is independent, after killing the main process, the other child processes will not be affected, and will continue to run, the above code in the process of the Targe function is very simple, only a limited number of cyclic output, and in the real scene, The child process may always be in the loop processing business, and if the child process is killed, the child process is not effectively recycled, it is necessary to kill manually, it is more troublesome.
In this case, the first thought is to use the signal signal to deal with, so that the killing of the main process can no longer use the kill-9 command, because the kill-9 command to send a Sigkill command to the process, while in the system, Sigkill and sigstop Two kinds of signals, The process cannot be captured and will exit immediately upon receipt. execute kill-l under Linux, you can see all the semaphore, here using the sigterm signal, sigterm means the stop signal, is the KILL command transmission system default signal, it differs from Sigkiil is that sigterm more friendly, The process can capture the sigterm signal, and then do some cleanup work as needed , making some changes to the above code after this is clear:
processes =[]def Fun (x): print ' current sub-process PID is%s '%os.getpid () whiletrue:print ' args is%s '%x Time.sleep (1) def term (sig_num, addtion): print ' Terminate process%d '%os.getpid ()Try: print ' The processes is%s '%Processes forPinchprocesses:print ' Process%d Terminate '%p.pid p.terminate () # Os.kill (P.pid, signal. SIGKILL) except Exception ase:print str (e)if__name__ = =' __main__ ': print ' current PID is%s '%os.getpid () forIinchRange3): T= Process (Target=fun, args=(str (i),)) T.daemon=True T.start () processes.append (t) signal.signal (signal. SIGTERM, term)Try: forPinchprocesses:p.join () except Exception ase:print str (e)
Run the above code, output the main process ID, and then send the sigterm signal to the main process via the kill-15 PID, and terminate the child process before the main process exits. However, when exiting, except captures exception information OSError: [Errno 4] interrupted system call, which indicates that the main process is signaled and exited when the child process joins. The program gets the expected result, when sending a sigterm signal to the main process, the first end of all child processes, and then the main process exits. Then use KILL-15 plus the process ID of the child process, send the sigterm signal to the child process to see if the child process can achieve the same effect, but after sending a signal to the process, does not enter the term function, through the PS can be seen, the child process received the sigterm signal, the self-exit, The main process and other child processes are not affected and are still functioning normally, and this does not get the same effect. We know that the child process inherits the signal processing mechanism of the parent process, but here the subprocess does not run the term function after receiving the SIGTERM signal, carefully observing the code example above, and noticing that the child process has started when registering the signal handler, so there is no signal processing function registered in the child process, and then, We modify the main process to ensure that the signal processing function is registered before the child process starts :
processes =[]def Fun (x): print ' current sub-process PID is%s '%os.getpid () whiletrue:print ' args is%s '%x Time.sleep (1) def term (sig_num, addtion): print ' Terminate process%d '%os.getpid ()Try: print ' The processes is%s '%Processes forPinchprocesses:print ' Process%d Terminate '%p.pid p.terminate () # Os.kill (P.pid, signal. SIGKILL) except Exception ase:print str (e)if__name__ = =' __main__ ': signal.signal (signal. SIGTERM, term) print ' current PID is%s '%os.getpid () forIinchRange3): T= Process (Target=fun, args=(str (i),)) T.daemon=True T.start () processes.append (t)Try: forPinchprocesses:p.join () except Exception ase:print str (e)
Run the program again, through the kill-15 to the parent process to send the sigterm signal, the process received a signal, but the program continues to run, observe the following output information, the main process received a signal, the execution of the term function, and by calling the child process of p.terminate (), Note that the Terminate is implemented under the Linux system as follows: Terminate the process. On Unix the-is-done using the SIGTERM signal, that is, when a child process calls P.terminate (), the SIGTERM signal is actually sent to the subprocess, Before we had placed the registration of the signal handler function before the child process was started, the child process was able to execute the signal processing function. from the output of the processes information can be seen, due to the boot order, the global processes variable is not well shared with the child process information. After receiving the semaphore sent by P.terminate (), the child process executes the term function, which attempts to kill the child process again by calling P.terminate (), thus entering an infinite loop, kill-15 sending a sigterm signal to the child process, will get the same result .
At this point, the basic understanding of how to send a semaphore to the main process to end the main process and its child process method, then there is no way to send a signal to the child process to achieve the same effect? The answer is yes, when we create a child process in the main process, the main process and the child process it creates belong to the same group, the concept of this grouping becomes a process group in Linux, it is a collection of one or more processes, and the process group IDs of the processes in the same process group are consistent. Using the Os.getpgid method in the Python standard library, the ID of the process is used to get the group ID of the process, and then the OS.KILLPG method is called to send a signal to the process's group ID , and now the above code is simply modified:
def Fun (x): print ' current PID is%s, group ID is%s '%(Os.getpid (), Os.getpgrp ()) whiletrue:print ' args is%s '%x Time.sleep (1) def term (sig_num, addtion): print ' current PID is%s, group ID is%s '%(Os.getpid (), Os.getpgrp ()) OS.KILLPG (Os.getpgid (Os.getpid ()), signal. SIGKILL)if__name__ = =' __main__ ': signal.signal (signal. SIGTERM, term) print ' current PID is%s '%os.getpid () forIinchRange3): T= Process (Target=fun, args=(str (i),)) T.daemon=True T.start () processes.append (t)Try: forPinchprocesses:p.join () except Exception ase:print str (e)
Note In the code, in order to prevent an infinite loop from appearing before, in the term function, we send the sigkill signal directly to the process group via OS.KILLPG. Running the code, we can see from the output that, in the process group, the process group ID of the main process and the child process is the same as the PID of the main process. When a sigterm signal is sent to the main or child process through kill-15, both the process group master and child processes are killed:
How to ensure that the child process exits at the same time without becoming an orphan process when the main process is killed