Python 垃圾收集异常时出现死锁

问题描述 投票:0回答:1

我遇到了一个奇怪的情况,由于python处理异常的方式,程序不会退出。在这种情况下,我有一个拥有一个线程的对象,并且该线程仅在调用该对象的

__del__
方法时才会关闭。但是,如果程序由于涉及该对象的异常而“退出”,则异常本身将在其堆栈跟踪中保存对该对象的引用,这会阻止该对象被删除。由于对象没有被删除,线程永远不会关闭,因此程序无法完全退出并永远挂起。这是一个小重现:

import threading

class A:

  def __init__(self):
    self._event = threading.Event()
    self._thread = threading.Thread(target=self._event.wait)
    self._thread.start()

  def __del__(self):
    print('del')
    self._event.set()
    self._thread.join()

def main():
  a = A()
  # The stack frame created here holds a reference to `a`, which
  # can be verified by looking at `gc.get_referrers(a)` post-raise.
  raise RuntimeError()

main() # hangs indefinitely

一种解决方法是通过消除异常并引发新异常来破坏引用链:

error = False
try:
  main()
except RuntimeError as e:
  error = True

if error:
  # At this point the exception should be unreachable; however in some
  # cases I've found it necessary to do a manual garbage collection.
  import gc; gc.collect()
  # Sadly this loses the stack trace, but that's what's necessary.
  raise RuntimeError()

有趣的是,只要在主模块中留下对

a
的引用,就会出现类似的问题,没有任何例外:

A()  # This is fine, prints 'del'
a = A()  # hangs indefinitely

这是怎么回事?这是 python (3.10) 的错误吗?是否有避免此类问题的最佳实践?我真的花了很长时间才弄清楚发生了什么!

python exception garbage-collection python-multithreading deadlock
1个回答
1
投票

基于Python的数据模型

不保证为对象调用

__del__()
方法 当解释器退出时仍然存在。

所以你不应该在

__del__
函数中终止线程。相反,建议在适当的时候显式设置标志来发出终止信号,或者您可以使用上下文管理器:

import threading

class A:

  def __init__(self):
    self._event = threading.Event()
    
  def __enter__(self):
      self._thread = threading.Thread(target=self._event.wait)
      self._thread.start()
      
  def __exit__(self):
      self._event.set()
      self._thread.join()

def main():
  with A() as a:
    raise RuntimeError()

main()
© www.soinside.com 2019 - 2024. All rights reserved.