Python子进程:cmd退出时的回调
我目前正在使用 subprocess.Popen(cmd, shell=TRUE) 启动一个程序
我对 Python 相当陌生,但“感觉”应该有一些 api 可以让我做一些事情类似于:
subprocess.Popen(cmd, shell=TRUE, postexec_fn=function_to_call_on_exit)
我这样做是为了让 function_to_call_on_exit
可以在知道 cmd 已退出的情况下做一些事情(例如,记录当前正在运行的外部进程的数量)
我认为我可以相当简单地换行子进程在一个将线程与 Popen.wait() 方法相结合的类中,但由于我还没有在 Python 中完成线程,而且看起来这对于 API 的存在来说可能足够常见,所以我我想我应该先尝试找到一个。
提前致谢 :)
I'm currently launching a programme using subprocess.Popen(cmd, shell=TRUE)
I'm fairly new to Python, but it 'feels' like there ought to be some api that lets me do something similar to:
subprocess.Popen(cmd, shell=TRUE, postexec_fn=function_to_call_on_exit)
I am doing this so that function_to_call_on_exit
can do something based on knowing that the cmd has exited (for example keeping count of the number of external processes currently running)
I assume that I could fairly trivially wrap subprocess in a class that combined threading with the Popen.wait()
method, but as I've not done threading in Python yet and it seems like this might be common enough for an API to exist, I thought I'd try and find one first.
Thanks in advance :)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(10)
你是对的 - 没有很好的 API 可以做到这一点。您的第二点也是正确的 - 设计一个使用线程为您执行此操作的函数非常容易。
即使线程在 Python 中也很容易,但请注意,如果 on_exit() 的计算量很大,您将需要将其放在单独的进程中,而不是使用多处理(这样 GIL 就不会减慢您的程序速度)。它实际上非常简单 - 您基本上只需用
multiprocessing.Process
替换对threading.Thread
的所有调用,因为它们遵循(几乎)相同的 API。You're right - there is no nice API for this. You're also right on your second point - it's trivially easy to design a function that does this for you using threading.
Even threading is pretty easy in Python, but note that if on_exit() is computationally expensive, you'll want to put this in a separate process instead using multiprocessing (so that the GIL doesn't slow your program down). It's actually very simple - you can basically just replace all calls to
threading.Thread
withmultiprocessing.Process
since they follow (almost) the same API.Python 3.2 中有
concurrent.futures
模块(对于较旧的 Python <3.2,可通过pip install futures
获取):回调将在调用
f.add_done_callback()
的同一进程中调用。完整程序
输出
There is
concurrent.futures
module in Python 3.2 (available viapip install futures
for older Python < 3.2):The callback will be called in the same process that called
f.add_done_callback()
.Full program
Output
我修改了 Daniel G 的答案,将
subprocess.Popen
args
和kwargs
作为它们本身而不是作为单独的元组/列表传递,因为我想要将关键字参数与subprocess.Popen
结合使用。就我而言,我有一个方法
postExec()
,我想在subprocess.Popen('exe', cwd=WORKING_DIR)
之后运行它,使用下面的代码,它就变成了
popenAndCall(postExec, 'exe', cwd=WORKING_DIR)
I modified Daniel G's answer to simply pass the
subprocess.Popen
args
andkwargs
as themselves instead of as a separate tuple/list, since I wanted to use keyword arguments withsubprocess.Popen
.In my case I had a method
postExec()
that I wanted to run aftersubprocess.Popen('exe', cwd=WORKING_DIR)
With the code below, it simply becomes
popenAndCall(postExec, 'exe', cwd=WORKING_DIR)
我遇到了同样的问题,并使用 multiprocessing.Pool 解决了它。这里涉及到两个技巧:
结果是一个在完成时执行回调的函数
在我的例子中,我也希望调用是非阻塞的。做工精美
I had same problem, and solved it using
multiprocessing.Pool
. There are two hacky tricks involved:result is one function executed with callback on completion
In my case, I wanted invocation to be non-blocking as well. Works beautifully
在 POSIX 系统上,当子进程退出时,父进程会收到 SIGCHLD 信号。要在子进程命令退出时运行回调,请在父进程中处理 SIGCHLD 信号。像这样的东西:
请注意,这在 Windows 上不起作用。
On POSIX systems, the parent process receives a SIGCHLD signal when a child process exits. To run a callback when a subprocess command exits, handle the SIGCHLD signal in the parent. Something like this:
Note that this will not work on Windows.
我受到 Daniel G.answer 的启发,实现了一个非常简单的用例 - 在我的工作中,我经常需要使用不同的参数重复调用相同的(外部)进程。我已经找到了一种方法来确定每个特定调用何时完成,但现在我有一种更简洁的方法来发出回调。
我喜欢这个实现,因为它非常简单,但它允许我向多个处理器发出异步调用(请注意,我使用
多处理
而不是线程
)并在完成时接收通知。我测试了示例程序并且运行良好。请随意编辑并提供反馈。
示例输出:
下面是
sleeper.c
的源代码 - 我的示例“耗时”外部进程编译为:
I was inspired by Daniel G. answer and implemented a very simple use case - in my work I often need to make repeated calls to the same (external) process with different arguments. I had hacked a way to determine when each specific call was done, but now I have a much cleaner way to issue callbacks.
I like this implementation because it is very simple, yet it allows me to issue asynchronous calls to multiple processors (notice I use
multiprocessing
instead ofthreading
) and receive notification upon completion.I tested the sample program and works great. Please edit at will and provide feedback.
Sample output:
Below is the source code of
sleeper.c
- my sample "time consuming" external processcompile as:
从 3.2 开始,concurrent.futures 中也有 ProcesPoolExecutor (https://docs.python .org/3/library/concurrent.futures.html)。用法与上面提到的ThreadPoolExecutor一样。通过 executor.add_done_callback() 附加退出回调。
There is also ProcesPoolExecutor since 3.2 in concurrent.futures (https://docs.python.org/3/library/concurrent.futures.html). The usage is as of the ThreadPoolExecutor mentioned above. With on exit callback being attached via executor.add_done_callback().
谢谢你们为我指明了正确的方向。我根据在这里找到的内容创建了一个类,并添加了一个停止函数来终止该进程:
Thanks guys, for pointing me into the right direction. I made a class from what I found here and added a stop-function to kill the process:
当前对该问题的大多数答案都建议每个进程旋转一个线程只是为了等待该回调。在我看来,这是不必要的浪费:单个线程应该足以满足以这种方式创建的所有进程的所有回调。
另一个答案建议使用信号,但这会产生竞争条件,在上一个调用完成之前,信号处理程序可能会再次被调用。在 Linux 上,
signalfd(2)
可以帮助解决这个问题,但 Python 不支持 (尽管通过ctypes
添加很容易)。Python 中
asyncio
使用的替代方法是使用signal.set_wakeup_fd
。然而,还有另一种解决方案,基于操作系统将在进程退出时关闭所有打开的 fd:如果不需要支持 MacOS,
select.epoll
可能是更好的选择,因为它允许持续更新轮询。Most of the current answers to this question suggest spinning up one thread per process just to wait for that callback. That strikes me as needlessly wasteful: A single thread should suffice for all callbacks from all processes created this way.
Another answer suggests using signals, but that creates a race condition where the signal handler might get called again before the previous call finished. On Linux,
signalfd(2)
could help with that but it's not supported by Python (although it's easy enough to add viactypes
).The alternative used by
asyncio
in Python is to usesignal.set_wakeup_fd
. However, there is another solution based on the fact that the OS will close all open fds on process exit:If supporting MacOS isn't a requirement
select.epoll
is likely a better choice as it allows updating ongoing polling.AFAIK 没有这样的 API,至少在
subprocess
模块中没有。您需要自己滚动一些东西,可能使用线程。AFAIK there is no such API, at least not in
subprocess
module. You need to roll something on your own, possibly using threads.