windows 下多线程执行 subporcess.Popen(),但子进程无法杀死

2023-03-14 02:07:00 +08:00
 evemoo

该文章提到的 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)
1070 次点击
所在节点    Python
6 条回复
ClericPy
2023-03-14 18:53:35 +08:00
孤儿进程吗? 杀了以后 wait 试一下
evemoo
2023-03-14 19:36:36 +08:00
@ClericPy
多线程里面 wait 会死锁,官方文档推荐 communicate()方法
evemoo
2023-03-14 20:00:23 +08:00
> 如果 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)
evemoo
2023-03-14 20:48:15 +08:00
总算解决了,某种意义上算是给自己埋坑。
第一个点,可执行程序如果有-d 参数这种后台运行的参数,subprocess 调用的时候要去掉,不然会以独立进程存在。
第二个点,不用 communicate(),统一输出 log 到文件。communicate()加上 timeout 参数捕获异常就无法 kill 子进程,实在是诡异
yinmin
2023-03-24 18:24:36 +08:00
proc.terminate() 改成 proc.kill() 试试。linux 一定能杀掉,windows 不知道是否可行。
evemoo
2023-03-24 18:34:32 +08:00
@yinmin 是的,树莓派下 proc.kill() 无任何问题,windows 就是杀不掉

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/923742

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX