为什么删除包含对象引用的集合时不调用“obj.__del__()”

Why is `obj.__del__()` not being called when collection containing object reference is deleted

提问人:vahvero 提问时间:4/18/2023 最后编辑:nigh_anxietyvahvero 更新时间:4/19/2023 访问量:58

问:

我正在尝试构建一个子类以在我的桌面中使用多个 GPU。Process

class GPUProcess(mp.Process):
    used_ids: list[int] = []
    next_id: int = 0

    def __init__(self, *, target: Callable[[Any], Any], kwargs: Any):
        gpu_id = GPUProcess.next_id
        if gpu_id in GPUProcess.used_ids:
            raise RuntimeError(
                f"Attempt to reserve reserved processor {gpu_id} {self.used_ids=}"
            )
        GPUProcess.next_id += 1
        GPUProcess.used_ids.append(gpu_id)
        self._gpu_id = gpu_id

        # Define target process func with contant gpu_id
        def _target(**_target_kwargs):
            target(
                **_target_kwargs,
                gpu_id=self.gpu_id,
            )
        super(GPUProcess, self).__init__(target=_target, kwargs=kwargs)

    @property
    def gpu_id(self):
        return self._gpu_id

    def __del__(self):
        GPUProcess.used_ids.remove(self.gpu_id)

    def __repr__(self) -> str:
        return f"<{type(self)} gpu_id={self.gpu_id} hash={hash(self)}>"
# Test creation
def test_process_creation():
    # Expect two gpus
    def dummy_func(*args):
        return args
    processes = []
    for _ in range(2):
        p = GPUProcess(
            target=dummy_func,
            kwargs=dict(a=("a", "b", "c")),
        )
        processes.append(p)
    
    for p in processes:
        p.start()

    for p in processes:
        p.join()

    del processes

    assert GPUProcess.used_ids == [], f"{GPUProcess.used_ids=}!=[]"

if __name__ == "__main__":
    test_process_creation()

__del__不为第二个进程调用。

AssertionError: GPUProcess.used_ids=[1]!=[]

为什么不叫第二个?__del__

稍后,我将利用这个类来运行大量有效负载,每个 GPU 使用一个有效负载和一个使用关键字来决定使用设备的函数。这在 Python 中是否明智?mp.PoolGPUProcessgpu_id

python 多处理 del

评论


答:

2赞 nigh_anxiety 4/18/2023 #1

简短的回答是,没有调用,因为前一个 for 循环中的变量仍在引用第二个进程对象__del__p

object.__del__根据官方文档,不保证在被调用时被调用。del object

object.__del__当对象的引用计数为 0 时调用。
关键字将对象的引用计数减少 1。
del

因此,您可以通过设置或在调用断言之前删除该引用来解决此问题,或者在退出函数后调用断言,因为这将从该堆栈级别中删除所有引用计数。p = Nonedel ptest_process_creation()

我发现这个来自 mCoding 频道的视频非常有用,以及如何不使用它。__del__

顺便说一句,我要指出的是,在使用这些更改测试您的代码时,我发现您需要解决几个问题:

  1. 通过断言后,我遇到了错误。你的方法应该 使用 try/except,或检查self.gpu_id是否在列表中 在删除它之前。ValueError: list.remove(x): x not in list__del__()
  2. 在中,您将虚拟函数定义为 ,但您创建的测试进程专门定义了关键字参数。这将导致异常 。最简单的解决方案是将定义更改为test_process_creation()dummy_func(*args)kwargs=dict(a=("a", "b", "c"))TypeError: test_process_creation.<locals>.dummy_func() got an unexpected keyword argument 'a'dummy_func(**kwargs)

评论

0赞 vahvero 4/19/2023
答案是肯定的。我真傻。你对这个问题的一般方法有什么看法吗?
1赞 nigh_anxiety 4/19/2023
@vahvero 跟踪和清理进程 ID 的问题?或者最后一段中提出的关于这是否是一种明智的方法的问题?我没有在 Python 中做过太多的多处理,所以我不能对此发表强烈意见。可能值得作为一个单独的问题来问这种方法是否正确(甚至可能存在一个现有的问题)。我的一个建议是考虑在一个集合而不是列表中跟踪使用过的gpu_ids。由于所有的 gpu 都是整数,因此它们是可散列的,并且从集合中添加和删除元素是 O(1)
1赞 juanpa.arrivillaga 4/19/2023
@vahvero根本不用于此目的,请使用上下文管理器__del__
0赞 vahvero 4/19/2023
我认为这是不可能的,因为该过程必须由国会议员使用。池