该文章提到的 shell=False
不起作用。
简单说明:
一个可执行文件读取配置文件(非常多)在两秒内返回结果,即便修改 _work_queue
还是会因为无法关闭相应子进程导致内存溢出。
import shlex
from concurrent.futures import ThreadPoolExecutor
class BoundedThreadPoolExecutor(ThreadPoolExecutor):
def __init__(self, max_workers, max_waiting_tasks, *args, **kwargs):
super().__init__(max_workers=max_workers, *args, **kwargs)
self._work_queue = Queue(maxsize=max_waiting_tasks)
def func(conf_path):
try:
command = f"../some.exe -d -c {conf_path}"
args = shlex.split(command)
proc = subprocess.Popen(args=args, shell=False, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
stdout, stderr = proc.communicate(5)
except subprocess.TimeoutExpired:
logger.error(f"pid:{proc.pid} communicate timeout")
finally:
proc.terminate()
return proc
def callback2kill(res):
proc = res.result()
os.system(f"taskkill /F /pid {proc.pid}")
with BoundedThreadPoolExecutor(100, 500) as executor:
for endpoint in endpoints:
future = executor.submit(func, endpoint)
future.add_done_callback(callback2kill)
1
ClericPy 2023-03-14 18:53:35 +08:00
孤儿进程吗? 杀了以后 wait 试一下
|
3
evemoo OP > 如果 shell=True ,那么会通过 shell 来启动进程。这意味着,一次 Popen 会启动两个进程,一个 shell 进程,一个命令进程。然后 Popen 返回的 pid 是 shell 进程的 pid ,这会导致 Popen.kill() 等函数不起作用,进程还在正常运行,所以一定要使用参数列表的形式启动,不要通过命令行的形式,不要使用 shell=True 。
但是还是解决不了子进程问题 简单复现: run.bat 写入 ping -n 10 127.0.0.1 启动一个线程执行 subprocess.Popen(args="run.bat"),communicate(timeout=1),捕获 subprocess.TimeoutExpired 。 然后发现依然是要等 run.bat 程序 ping 完 10 次才会退出程序,但纯命令行执行 subprocess.Popen(args="ping -n 10 127.0.0.1")能捕获到并提前 kill 。 ![image]( https://user-images.githubusercontent.com/42791572/224993848-874948bb-2836-4995-90e0-4faccb4ea861.png) |
4
evemoo OP 总算解决了,某种意义上算是给自己埋坑。
第一个点,可执行程序如果有-d 参数这种后台运行的参数,subprocess 调用的时候要去掉,不然会以独立进程存在。 第二个点,不用 communicate(),统一输出 log 到文件。communicate()加上 timeout 参数捕获异常就无法 kill 子进程,实在是诡异 |
5
yinmin 2023-03-24 18:24:36 +08:00
proc.terminate() 改成 proc.kill() 试试。linux 一定能杀掉,windows 不知道是否可行。
|