第十章:使用进程、线程和协程提供并发性-subprocess:创建附加进程-进程间传递信号

10.1.5 进程间传递信号
os模块的进程管理例子演示了如何使用os.fork()和os.kill()在进程间传递信号。由于每个Popen实例提供了一个pid属性,其中包含子进程的进程ID,所以可以完成类似于subprocess的工作。
下一个例子结合了两个脚本。这个子进程的USR信号建立了一个信号处理器。

# signal_child.py
import os
import signal
import time
import sys

pid = os.getpid()
received = False

def signal_usr1(signum,frame):
    "Callback invoked when a signal is received"
    global received
    received = True
    print('CHILD {:>6}: Received USR1'.format(pid))
    sys.stdout.flush()

print('CHILD {:>6}: Setting up signal handler'.format(pid))
sys.stdout.flush()
signal.signal(signal.SIGUSR1,signal_usr1)
print('CHILD {:>6}: Pausing to wait for signal'.format(pid))
sys.stdout.flush()
time.sleep(3)

if not received:
    print('CHILD {:>6}: Never received signal'.format(pid))

这个脚本作为父进程运行。它启动signal_child.py,然后发送USR1信号。

import os
import signal
import subprocess
import time
import sys

proc = subprocess.Popen(['python3','signal_child.py'])
print('PARENT       :Pausing before sending signal...')
sys.stdout.flush()
time.sleep(1)
print('PARENT       :Signaling child')
sys.stdout.flush()
os.kill(proc.pid,signal.SIGUSR1)

输出如下所示。
在这里插入图片描述
进程组/会话
如果Popen创建的进程创建了子进程,那么这些子进程不会接收发送给父进程的信号。这说明,使用Popen的shell参数时,很难通过发送SIGINT或SIGTERM来终止在shell中启动的命令。

import os
import signal
import subprocess
import tempfile
import time
import sys

script = '''#!/bin/sh
echo "Shell script in process $$"
set -x
python3 signal_child.py
'''
script_file = tempfile.NamedTemporaryFile('wt')
script_file.write(script)
script_file.flush()

proc = subprocess.Popen(['sh',script_file.name])
print('PARENT      :Pausing before signaling {}...'.format(
    proc.pid))
sys.stdout.flush()
time.sleep(1)
print('PARENT      :Signaling child {}'.format(proc.pid))
sys.stdout.flush()
os.kill(proc.pid,signal.SIGUSR1)
time.sleep(3)

在这个例子中,发送信号所用的pid与等待信号的shell脚本子进程的pid不匹配,因为有3个不同的进程在交互。
运行结果:
在这里插入图片描述
如果向子进程发送信号但不知道它们的进程ID,那么可以使用一个进程组(process group)管理这些子进程,使它们能一同收到信号。进程组用os.setpgrp()创建,将进程组ID设置为当前进程的进程ID。所有子进程都会从其父进程继承进程组。由于这个组只能在由Popen及其子进程创建的shell中设置,所以不能在创建Popen的同一进程中调用os.setpgrp()。实际上,这个函数要作为preexec_fn参数传至Popen,以便其在新进程执行
fork()之后运行,之后才能使用exec()运行shell。要向整个进程组发送信号,可以使用os.killpg()并提供Popen实例的pid值。

import os
import signal
import subprocess
import tempfile
import time
import sys

def show_setting_prgrp():
    print('Calling os,setpgrp() from {}'.format(os.getpid()))
    os.setpgrp()
    print('Process group is now {}'.format(
        os.getpid(),os.getpgrp()))
    sys.stdout.flush()

script = '''#!/bin/sh
echo "Shell script in process $$"
set -x
python3 signal_child.py
'''

script_file = tempfile.NamedTemporaryFile('wt')
script_file.write(script)
script_file.flush()

proc = subprocess.Popen(
    ['sh',script_file.name],
    preexec_fn=show_setting_prgrp,
    )
print('PARENT       :Pausing before signaling {}...'.format(
    proc.pid))
sys.stdout.flush()
time.sleep(1)
print('PARENT       :Signaling process group {}'.format(
    proc.pid))
sys.stdout.flush()
os.killpg(proc.pid,signal.SIGUSR1)
time.sleep(3)

时间序列如下:
1.父程序实例化Popen
2.Popen实例创建一个新进程。
3.这个新进程运行os.setpgrp()。
4.这个新进程运行exec()以启动shell。
5.shell运行shell脚本。
6.shell脚本再次创建新进程,该进程执行Python。
7.Python运行signal_child.py
8.父程序使用shell的pid向进程组传送信号。
9.shell和Python进程接收到信号。
10.shell忽略这个信号。
11.运行signal_child.py的Python进程调用信号处理器。

运行结果:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_43193719/article/details/89478820