V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
evemoo
V2EX  ›  Python

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

  •  
  •   evemoo · 2023-03-14 02:07:00 +08:00 · 1209 次点击
    这是一个创建于 620 天前的主题,其中的信息可能已经有所发展或是发生改变。

    该文章提到的 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)
    
    6 条回复    2023-03-24 18:34:32 +08:00
    ClericPy
        1
    ClericPy  
       2023-03-14 18:53:35 +08:00
    孤儿进程吗? 杀了以后 wait 试一下
    evemoo
        2
    evemoo  
    OP
       2023-03-14 19:36:36 +08:00
    @ClericPy
    多线程里面 wait 会死锁,官方文档推荐 communicate()方法
    evemoo
        3
    evemoo  
    OP
       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
        4
    evemoo  
    OP
       2023-03-14 20:48:15 +08:00
    总算解决了,某种意义上算是给自己埋坑。
    第一个点,可执行程序如果有-d 参数这种后台运行的参数,subprocess 调用的时候要去掉,不然会以独立进程存在。
    第二个点,不用 communicate(),统一输出 log 到文件。communicate()加上 timeout 参数捕获异常就无法 kill 子进程,实在是诡异
    yinmin
        5
    yinmin  
       2023-03-24 18:24:36 +08:00
    proc.terminate() 改成 proc.kill() 试试。linux 一定能杀掉,windows 不知道是否可行。
    evemoo
        6
    evemoo  
    OP
       2023-03-24 18:34:32 +08:00
    @yinmin 是的,树莓派下 proc.kill() 无任何问题,windows 就是杀不掉
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2108 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 00:23 · PVG 08:23 · LAX 16:23 · JFK 19:23
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.