繁体   English   中英

Python 带有自定义进程的进程池无法重生子进程

[英]Python Process Pool with custom Process not able to respawn child processes

我已经像这样覆盖了multiprocess.Process (多处理库的分支):

# source.Process.py

class Process(multiprocess.Process):

def __init__(self, test_name: str, *args, **kwargs) -> None:
  
    multiprocess.Process.__init__(self, *args, **kwargs)
    self._parent_conn, self._child_conn = multiprocess.Pipe()
    self._exception = None
    self._test_name = test_name

def run(self) -> None:
    try:
        start = time.perf_counter()
        logger = S_Logger(self._test_name).get_logger()
        logger.info('EXECUTION OF %s HAS STARTED.', self._test_name)
        multiprocess.Process.run(self)
        self._child_conn.send(None)

    except Exception as e:
        tb = traceback.format_exc()
        logger.error(f'EXCEPTION OCCURRED: {tb}')
        self._child_conn.send((e, tb))

    finally:
        logger.info('EXECUTION OF %s HAS ENDED.', self._test_name)
        end = time.perf_counter()
        logger.info(f'FINISHED in {round(end-start, 2)} second(s)')

当我使用此 class 创建正常流程时,一切正常,包括创建日志。 现在我想创建一个这样的自定义进程的进程池,但是我遇到了在它们生命结束后重生这些进程的问题。 这是我使用附加maxtasksperchild=1参数创建池的方法。

from source.process import Process
ctx = multiprocess.get_context()

def run_tests(self):

    def worker(x):
        print(x**2)
        time.sleep(1)

    with ctx.Pool(processes=4, maxtasksperchild=1) as pool:

        nums = range(10)
        ctx.Process = Process(test_name='test_name')
        pool.map(worker, nums)

这给了我这样的 output:

0
1
4
9
Exception in thread Thread-1 (_handle_workers):
Traceback (most recent call last):
  File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.10_3.10.2032.0_x64__qbz5n2kfra8p0\lib\threading.py", line 1016, in _bootstrap_inner
    self.run()
  File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.10_3.10.2032.0_x64__qbz5n2kfra8p0\lib\threading.py", line 953, in run
    self._target(*self._args, **self._kwargs)
  File "C:\Users\<user>\Documents\Projects\sprinter\.venv\lib\site-packages\multiprocess\pool.py", line 513, in _handle_workers
    cls._maintain_pool(ctx, Process, processes, pool, inqueue,
  File "C:\Users\<user>\Documents\Projects\sprinter\.venv\lib\site-packages\multiprocess\pool.py", line 337, in _maintain_pool
    Pool._repopulate_pool_static(ctx, Process, processes, pool,
  File "C:\Users\<user>\Documents\Projects\sprinter\.venv\lib\site-packages\multiprocess\pool.py", line 319, in _repopulate_pool_static
    w = Process(ctx, target=worker,
  File "C:\Users\<user>\Documents\Projects\sprinter\.venv\lib\site-packages\multiprocess\pool.py", line 181, in Process
    return ctx.Process(*args, **kwds)
TypeError: 'Process' object is not callable

这让我想到了两个问题:

  1. 为什么没有日志记录? 如果我不使用池,日志会正确显示。
  2. 为什么在执行了四个进程之后,应该重新生成的新进程有问题要创建? (不可调用错误)。 如果我删除maxtasksperchild参数,它会完美运行(0、1、4、9、16、25 ...)

这给了我这样的 output

这里的错误是因为您将 ctx.Process (一个类)替换为您自己的子类的实例 实例,除非它们定义了__call__方法,否则是不可调用的。 但即使你用你的子类替换它,它也行不通。 这是因为您将收到递归或属性错误,因为您将 class 替换为同一 class 的子类。

为什么没有日志记录? 如果我不使用池,日志会正确显示。

这是因为您从未真正成功地修补池 class 以使用您的 Process 子类,这也与您的第二个问题有关(请继续阅读)。

为什么在执行了四个进程之后,应该重新生成的新进程有问题要创建? (不可调用错误)。 如果我删除 maxtasksperchild 参数,它会完美运行(0、1、4、9、16、25 ...)

发生这种情况的原因是因为 pool 在您启动上下文管理器本身时创建了进程(在线ctx.Pool(processes=4, maxtasksperchild=1) as pool )。 由于您是在进程启动后应用补丁,因此除非池再次启动进程(这是maxtasksperchild的用武之地),否则它不会产生太大影响。 因此,如果您提供maxtasksperchild ,则池将尝试启动另一个进程,但由于补丁错误,它将返回错误。 如果您没有设置maxtasksperchild ,那么池将不会关心您应用的补丁,因为它不必再次启动进程。

无论如何,这里有一个更好的补丁来做你想做的事

from multiprocess.pool import Pool
from functools import partial
import multiprocess
import time

class Process(multiprocess.Process):

    def __init__(self, *args, test_name='', **kwargs) -> None:

        multiprocess.Process.__init__(self, *args, **kwargs)
        self._parent_conn, self._child_conn = multiprocess.Pipe()
        self._exception = None
        self._test_name = test_name

    def run(self) -> None:
        # Have your own implementation here
        pass


def _Process(ctx, *args, **kwds):
    return ctx.MyProcess(*args, **kwds)

def worker(x):
    print(x ** 2)
    time.sleep(1)

if __name__ == "__main__":
    ctx = multiprocess.get_context()

    # Some patching, we add our subclass as an attribute to the context
    ctx.MyProcess = Process
    
    # Fix test_name to be passed as a kwarg whenever the pool starts a process. Pretty lazy but gets the job done. 
    test_name = 'test_name'
    Pool.Process = partial(_Process, test_name=test_name)

    with ctx.Pool(processes=4, maxtasksperchild=1) as pool:
        nums = range(10)
        pool.map(worker, nums)

注意test_name现在是一个关键字参数并且也是可选的。 这是为了使它与functools.partial一起工作。 您可能希望执行检查以使值通过并且有效。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM