【问题标题】:terminating a function call in python after n secondsn秒后终止python中的函数调用
【发布时间】:2015-03-16 10:43:57
【问题描述】:

我的python代码是这样的:

def a():
    ...  
    ...  
    subprocess.call()  
    ...  
    ...  

def b():  
    ...  
    ...  

等等。

我的任务:
1)如果subprocess.call()在3秒内返回,我的执行应该在subprocess.call()返回的那一刻继续。
2)如果subprocess.call()在3秒内没有返回,subprocess.call()应该被终止,我的执行应该在3秒后继续。
3) 在subprocess.call() 返回或3 秒结束之前,不应进行进一步的执行。

这可以通过线程来完成,但是如何实现呢?

实际代码的相关部分是这样的:

...  
cmd = ["gcc", "-O2", srcname, "-o", execname];    
p = subprocess.Popen(cmd,stderr=errfile)//compiling C program  
...  
...  
inputfile=open(input,'w')  
inputfile.write(scanf_elements)  
inputfile.close()  
inputfile=open(input,'r')  
tempfile=open(temp,'w')
subprocess.call(["./"+execname,str(commandline_argument)],stdin=inputfile,stdout=tempfile); //executing C program
tempfile.close()
inputfile.close()  
...  
...  

我正在尝试使用 python 编译和执行 C 程序。 当我使用 subprocess.call() 执行 C 程序并假设 C 程序包含无限循环时,subprocess.call() 应该在 3 秒后终止,程序应该继续。我应该能够知道 subprocess.call() 是否被强制终止或成功执行,以便我可以相应地在以下代码中打印消息。

后端gcc是linux的。

【问题讨论】:

标签: c linux python-2.7 subprocess


【解决方案1】:

下面的代码终于奏效了:

import subprocess
import threading
import time


def process_tree_kill(process_pid):
    subprocess.call(['taskkill', '/F', '/T', '/PID', process_pid])

def main():
    cmd = ["gcc", "-O2", "a.c", "-o", "a"];  
    p = subprocess.Popen(cmd)
    p.wait()
    print "Compiled"
    start = time.time()

    process = subprocess.Popen("a",shell=True)
    print(str(process.pid))   

    # terminate process in timeout seconds
    timeout = 3 # seconds
    timer = threading.Timer(timeout, process_tree_kill,[str(process.pid)])
    timer.start()

    process.wait()
    timer.cancel()

    elapsed = (time.time() - start)
    print elapsed

if __name__=="__main__":
    main()

【讨论】:

  • 1.您可以使用call(cmd) 而不是Popen(cmd).wait() 2. drop shell=True: (a) 如果 Popen 找不到可执行文件,请提供完整路径(目录和文件扩展名)(如何找到程序有不同的规则) . (b) process.terminate 代替 kill_process_tree 就足够了(没有父 shell 进程,只有 C 子进程)
【解决方案2】:

我的任务:
1) 如果 subprocess.call() 在 3 秒内返回,我的 执行应该在 subprocess.call() 返回的那一刻继续。
2) 如果 subprocess.call() 没有在 3 秒内返回, subprocess.call() 应该被终止,我的执行应该 3 秒后继续。
3) 直到 subprocess.call() 返回或 3 秒结束,不应再执行。

在 *nix 上,您可以使用 signal.alarm()-based solution:

import signal
import subprocess

class Alarm(Exception):
    pass

def alarm_handler(signum, frame):
    raise Alarm

# start process
process = subprocess.Popen(*your_subprocess_call_args)

# set signal handler
signal.signal(signal.SIGALRM, alarm_handler)
signal.alarm(3) # produce SIGALRM in 3 seconds

try:
    process.wait() # wait for the process to finish
    signal.alarm(0) # cancel alarm
except Alarm: # subprocess does not return within 3 seconds
    process.terminate() # terminate subprocess
    process.wait()

这是一个基于threading.Timer() 的便携式解决方案:

import subprocess
import threading

# start process
process = subprocess.Popen(*your_subprocess_call_args)

# terminate process in 3 seconds
def terminate():
    if process.poll() is None:
        try:
            process.terminate()
        except EnvironmentError:
            pass # ignore 

timer = threading.Timer(3, terminate)
timer.start()
process.wait()
timer.cancel()

【讨论】:

  • 这里的问题是,如果 process.stdout.readline() 没有根据您的链接返回,它可能会阻塞。但我也想要这个功能。
  • @user8109:输出被重定向到您问题中的文件。在这种情况下,process.stdoutNone。如果您需要阅读输出,请更新您的问题并提供您实际使用的代码
  • 如果代码包含的scanf语句多于文件内容,C代码会卡住,你的代码也会卡住。我说的对吗?
  • 我显示的有问题的代码是我实际使用的。
  • 代码按原样工作。就试一试吧。如果 C 代码被卡住,则警报响起并调用 process.terminate()。如果代码未定义 SIGTERM 处理程序,则子进程退出。如果程序没有在 SIGTERM 上退出;你可以使用process.kill(),它几乎可以杀死任何东西。如果问题中显示的代码那么process.stdoutNone 并且您不能调用readline()
【解决方案3】:
#!/usr/bin/python

import thread
import threading
import time
import subprocess
import os 

ret=-1

def b(arg):
    global ret
    ret=subprocess.call(arg,shell=True);

thread.start_new_thread(b,("echo abcd",))
start = time.time()


while (not (ret == 0)) and ((time.time() - start)<=3):
    pass

if (not (ret == 0)) :
    print "failed"
    elapsed = (time.time() - start)
    print elapsed
    thread.exit()

elif (ret == 0):#ran before 3 sec
    print "successful"
    elapsed = (time.time() - start)
    print elapsed

我已经编写了上面的代码,它可以正常工作并满足我的所有约束。 链接https://docs.python.org/2/library/thread.html 说:

thread.exit() 引发 SystemExit 异常。当没有被捕获时,这将导致线程静默退出。

所以我想应该没有孤儿进程、资源阻塞等问题。请指教。

【讨论】:

  • 不要直接使用thread模块,而是使用threading模块。
  • 我之前使用过线程模块,但我不知道如何使用该模块有效地杀死线程。你能建议我一些方法来杀死我现在发布的代码中的 thread1 吗?
  • 你不会杀死线程。如果你想使用线程;使用基于 threading.Timer 的解决方案 -- 类似于 来自 the link that I've provided several times already 的解决方案。您的情况更简单,只需调用process.wait() 而不是使用deque() 读取输出
  • 虽然process.terminate()和process.kill()都成功终止了subprocess.Popen,但是subprocess.Popen启动的子进程a.exe(C可执行文件)并没有终止.我只是使用一个简单的无限循环作为输入 C 文件。 Windows 任务管理器显示 a.exe 消耗 50% 的 RAM(1.5 GB)
  • 刚刚发布了使用process.kill和threading.Timer的代码。你可以试试
【解决方案4】:

如果您愿意将调用转换为 Popen 构造函数而不是 call(与运行 gcc 的方式相同),那么解决此问题的一种方法是等待 3 秒,轮询子进程,然后根据其returncode 属性是否仍为None 采取措施。考虑以下高度人为的示例:

import sys
import time
import logging
import subprocess

logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', level=logging.INFO)

if __name__ == '__main__':
  logging.info('Main context started')
  procCmd = 'sleep %d' % int(sys.argv[1])
  proc = subprocess.Popen(procCmd.split())

  time.sleep(3)
  if proc.poll() is None:
    logging.warning('Child process has not ended yet, terminating now')
    proc.terminate()
  else:
    logging.info('Child process ended normally: return code = %s' % str(proc.returncode))

  logging.info('Main context doing other things now')
  time.sleep(5)
  logging.info('Main context ended')

这会导致不同的日志输出,具体取决于子进程是否在 3 秒内完成:

$ python parent.py 1
2015-01-18 07:00:56,639 INFO Main context started
2015-01-18 07:00:59,645 INFO Child process ended normally: return code = 0
2015-01-18 07:00:59,645 INFO Main context doing other things now
2015-01-18 07:01:04,651 INFO Main context ended
$ python parent.py 10
2015-01-18 07:01:05,951 INFO Main context started
2015-01-18 07:01:08,957 WARNING Child process has not ended yet, terminating now
2015-01-18 07:01:08,957 INFO Main context doing other things now
2015-01-18 07:01:13,962 INFO Main context ended

请注意,即使子进程比这更早完成,上述方法也将始终等待 3 秒。如果你想要不同的行为,你可以将上面的内容转换成一个循环,不断地轮询子进程——你只需要跟踪已经过去了多少时间。

【讨论】:

  • 我尝试使用您的代码来满足我的所有约束,但我不能。问题是我必须将 proc=subprocess.Popen 放在一个线程中,但我无法从外部访问它(用于检查其状态),因为我是 python 新手。我写了一个有效的代码。能否请您参考一下,看看使用 thread.exit() 是否有任何问题或其中有任何其他问题?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-27
相关资源
最近更新 更多