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

polhcujo  于 2023-04-19  发布在  Python
关注(0)|答案(1)|浏览(124)

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

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__没有被调用?
后来,我使用这个类和mp.Pool来运行一个大的有效负载集,每个GPU使用一个GPUProcess和一个使用gpu_id关键字来决定使用的设备的函数。这在Python中是明智的方法吗?

z4iuyo4d

z4iuyo4d1#

简短的回答是,__del__没有被调用,因为上一个for循环中的变量p仍在引用第二个process对象
根据官方文档,当调用del object时,不保证调用object.__del__
当对象的引用计数为0时,调用object.__del__
del关键字将对象的引用计数减少1。
因此,您可以通过在调用Assert以删除该引用之前设置p = Nonedel p来解决这个问题,或者在退出test_process_creation()函数之后调用Assert,因为这将删除该堆栈级别的所有引用计数。
我发现this video from the mCoding channel是关于__del__以及如何不使用它的非常有用的信息。
顺便说一句,我会注意到,在测试你的代码时,我发现了一些你需要解决的问题:
1.在传递Assert之后,我遇到了错误ValueError: list.remove(x): x not in list。您的__del__()方法应该使用try/except,或者在删除它之前检查self.gpu_id是否在列表中。
1.在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)

相关问题