Python threading.Thread、作用域与垃圾回收
假设我从 threading.Thread 这个类派生出一个新类:
from threading import Thread
class Worker(Thread):
def start(self):
self.running = True
Thread.start(self)
def terminate(self):
self.running = False
self.join()
def run(self):
import time
while self.running:
print "running"
time.sleep(1)
这个类的任何实例在启动线程后,必须先主动结束这个线程,才能被垃圾回收(因为线程本身会保持对它的引用)。这就成了一个问题,因为这完全违背了垃圾回收的目的。在这种情况下,如果有一个对象封装了一个线程,当这个对象的最后一个实例 超出作用域 时,会调用析构函数来结束线程并进行清理。因此,单靠析构函数
def __del__(self):
self.terminate()
是没办法解决这个问题的。
我认为,优雅地封装线程的唯一方法是使用低级的 thread
内置模块和 weakref
弱引用。或者我可能遗漏了一些基本的东西。那么有没有比把事情搞得一团糟的 weakref
代码更好的方法呢?
3 个回答
我猜你之前是用C++的,在那种语言里,变量的作用域和生命周期有很大的关系。但在Python以及其他一些自动管理内存的语言中,情况就不一样了。作用域不等于生命周期,因为垃圾回收是由解释器决定的,不是根据作用域的边界来进行的。特别是当你在做异步操作时,这个问题更值得注意,脑海中的警报声应该让你警觉起来!
你可以通过使用del来控制对象的生命周期。实际上,如果你去看看cpython垃圾回收模块的源代码,你会发现里面对有终结器(del方法)的对象有一种明显的、不太严肃的轻视态度,这应该告诉大家,只有在必要时才使用对象的生命周期。
你可以用sys.getrefcount(self)来判断什么时候该退出线程的循环。但我不太推荐这样做(试试看它返回的数字,你可能会不高兴)。如果想知道谁持有这个对象,可以用gc.get_referrers(self)来检查。引用计数可能会受到垃圾回收的影响。
此外,把线程的运行时间和对象的作用域或生命周期绑定在一起,99%的情况下都是错误的。连Boost库都不这样做。它特意定义了一种叫做“分离线程”的概念,来避免这个问题。http://www.boost.org/doc/libs/1_55_0/doc/html/thread/thread_management.html
为了补充@datenwolf的评论,这里有另一种方法可以处理对象被删除或者父线程结束的情况:
import threading
import time
import weakref
class Foo(object):
def __init__(self):
self.main_thread = threading.current_thread()
self.initialised = threading.Event()
self.t = threading.Thread(target=Foo.threaded_func,
args=(weakref.proxy(self), ))
self.t.start()
while not self.initialised.is_set():
# This loop is necessary to stop the main threading doing anything
# until the exception handler in threaded_func can deal with the
# object being deleted.
pass
def __del__(self):
print 'self:', self, self.main_thread.is_alive()
self.t.join()
def threaded_func(self):
self.initialised.set()
try:
while True:
print time.time()
if not self.main_thread.is_alive():
print('Main thread ended')
break
time.sleep(1)
except ReferenceError:
print('Foo object deleted')
foo = Foo()
del foo
foo = Foo()
你觉得用一个包装类怎么样呢?这个包装类里面有一个 Thread
对象,而不是直接继承 Thread
。
比如:
class WorkerWrapper:
__init__(self):
self.worker = Worker()
__del__(self):
self.worker.terminate()
然后在客户端代码中使用这些包装类,而不是直接使用线程。
或者我是不是漏掉了什么呢(: