Python threading.Thread、作用域与垃圾回收

5 投票
3 回答
7413 浏览
提问于 2025-04-17 07:33

假设我从 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 个回答

1

我猜你之前是用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

2

为了补充@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()
5

你觉得用一个包装类怎么样呢?这个包装类里面有一个 Thread 对象,而不是直接继承 Thread

比如:

class WorkerWrapper:
    __init__(self):
        self.worker = Worker()
    __del__(self):
        self.worker.terminate()

然后在客户端代码中使用这些包装类,而不是直接使用线程。

或者我是不是漏掉了什么呢(:

撰写回答