我正在使用python 3.7并遵循此documentation。我想要一个进程,该进程应产生一个子进程,等待其完成任务,并获取一些信息。我使用以下代码:
if __name__ == '__main__':
q = Queue()
p = Process(target=some_func, args=(q,))
p.start()
print q.get()
p.join()
当子进程正确完成时,这没有问题,并且工作正常,但是当我的子进程在完成之前终止时,问题就开始了。 在这种情况下,我的应用程序处于等待状态。
为q.get()
和p.join()
提供超时并不能完全解决问题,因为我想立即知道子进程已死亡,而不是等待超时。
另一个问题是q.get()
上的超时会产生一个异常,我希望避免这种异常。
有人可以建议我以更优雅的方式解决这些问题吗?
答案 0 :(得分:1)
队列和信号
一种可能性是注册一个信号处理程序,并使用它来传递哨兵值。
在Unix上,您可以在父级中处理SIGCHLD
,但这不是您的选择。根据信号模块docs:
在Windows上,只能使用SIGABRT,SIGFPE,SIGILL,SIGINT,SIGSEGV,SIGTERM或SIGBREAK调用signal()。
不确定是否通过Task-Manager杀死它会转化为SIGTERM
,但您可以尝试一下。
要处理SIGTERM
,您需要在子级中注册信号处理程序。
import os
import sys
import time
import signal
from functools import partial
from multiprocessing import Process, Queue
SENTINEL = None
def _sigterm_handler(signum, frame, queue):
print("received SIGTERM")
queue.put(SENTINEL)
sys.exit()
def register_sigterm(queue):
global _sigterm_handler
_sigterm_handler = partial(_sigterm_handler, queue=queue)
signal.signal(signal.SIGTERM, _sigterm_handler)
def some_func(q):
register_sigterm(q)
print(os.getpid())
for i in range(30):
time.sleep(1)
q.put(f'msg_{i}')
if __name__ == '__main__':
q = Queue()
p = Process(target=some_func, args=(q,))
p.start()
for msg in iter(q.get, SENTINEL):
print(msg)
p.join()
示例输出:
12273
msg_0
msg_1
msg_2
msg_3
received SIGTERM
Process finished with exit code 0
Queue&Process.is_alive()
即使与Task-Manager一起使用,您的用例听起来也无法排除强行杀死,所以我认为您最好使用不依赖信号的方法。
您可以在循环中检查您的进程p.is_alive()
,是否使用指定的queue.get()
调用timeout
并处理Empty
异常:
import os
import time
from queue import Empty
from multiprocessing import Process, Queue
def some_func(q):
print(os.getpid())
for i in range(30):
time.sleep(1)
q.put(f'msg_{i}')
if __name__ == '__main__':
q = Queue()
p = Process(target=some_func, args=(q,))
p.start()
while p.is_alive():
try:
msg = q.get(timeout=0.1)
except Empty:
pass
else:
print(msg)
p.join()
也可以避免出现异常,但是我不建议您这样做,因为您不会将等待时间花在“排队”上,从而降低了响应速度:
while p.is_alive():
if not q.empty():
msg = q.get_nowait()
print(msg)
time.sleep(0.1)
管道和Process.is_alive()
如果您打算每个孩子使用一个连接,则可以使用管道代替队列。比队列更高效
(安装在管道顶部),您可以使用multiprocessing.connection.wait
立即等待多个对象的就绪状态。
multiprocessing.connection.wait(object_list,timeout = None)
等待直到object_list中的对象准备就绪。返回对象列表中已准备好的那些对象的列表。如果超时是浮动的,则呼叫最多会阻塞很多秒。如果超时为“无”,则它将无限期阻塞。负超时等于零超时。
对于Unix和Windows,如果对象是可读的Connection对象,则该对象可以出现在object_list中。 连接且可读的socket.socket对象;要么 Process对象的sentinel属性。 当可以从中读取数据或另一端已关闭时,连接或套接字对象已准备就绪。
Unix :等待(对象列表,超时)几乎等同于select.select(对象列表,[],[],超时)。区别在于,如果select.select()被信号中断,则会引发OSError且错误号为EINTR,而wait()不会。
Windows :object_list中的项必须是可等待的整数句柄(根据Win32函数WaitForMultipleObjects()的文档所使用的定义),也可以是带有fileno()方法,该方法返回套接字句柄或管道句柄。 (请注意,管道句柄和套接字句柄不是可等待的句柄。)
3.3版中的新功能。 docs
您可以使用它同时等待进程的哨兵属性和管道的父端。
import os
import time
from multiprocessing import Process, Pipe
from multiprocessing.connection import wait
def some_func(conn_write):
print(os.getpid())
for i in range(30):
time.sleep(1)
conn_write.send(f'msg_{i}')
if __name__ == '__main__':
conn_read, conn_write = Pipe(duplex=False)
p = Process(target=some_func, args=(conn_write,))
p.start()
while p.is_alive():
wait([p.sentinel, conn_read]) # block-wait until something gets ready
if conn_read.poll(): # check if something can be received
print(conn_read.recv())
p.join()