Python 线程,线程未被通知停止

5 投票
4 回答
6428 浏览
提问于 2025-04-17 16:28

我想在主程序停止时,让一个Python线程也停止。这个线程是用来连接服务器的,后台线程负责保持连接,而前台线程则处理回调。下面是一个简单的例子。

#!/usr/bin/python
import time, threading
class test():
    running = False
    def __init__(self):
        print "init"
        self.running = True
        self.thread = threading.Thread(target = self.startThread)
        self.thread.start()

    def __del__(self):
        running = False
        print "del"

    def startThread(self):
        print "thread start"
        while self.running:
            time.sleep(1)
            print "thread running"

a = test()

当程序结束时,我本以为__del__()这个方法会被调用,这样后台线程就可以收到停止的通知,但实际上它是在后台线程停止后才被调用的。因为这个类是给其他人使用的,我不想强迫他们写额外的代码来显式地调用某个函数。

4 个回答

1

我之前也遇到过同样的问题,最后在另一个问题的回答中找到了答案。

你可以使用 daemon = True 这个标志。关于这个的详细说明可以查看3.x2.x的文档。

所以你的代码应该是这样的:

#!/usr/bin/python
import time, threading

class test():
    def __init__(self):
        print "init"
        self.thread = threading.Thread(target = self.startThread)
        self.thread.daemon = True
        self.thread.start()

    def startThread(self):
        print "thread start"
        while True:
            time.sleep(1)
            print("thread running")

a = test()

备注:这样做可能并不是很干净地结束你的线程,但它会在你退出程序时停止线程,我想这正是你想要的效果。

1

根据gnibbler的评论,使用上下文管理器来明确释放资源可能会更好。关于是否应该使用__del__来释放资源,大家的看法似乎不太一致。关于这个话题,有几篇不错的文章可以参考,分别是这里这里

如果你习惯了像C++这样的语言,在那里使用的是RAII,那么可能会有点难以适应Python中析构函数的调用时机,因为它们可能不会在你预期的时候被调用,甚至可能根本不会被调用,这通常是因为引用计数和垃圾回收的工作方式。

所以,在Python中,通常的做法是使用上下文管理器,这样可以明确地释放资源。

一个简单的线程示例可能看起来像这样(未经测试):

#!/usr/bin/python
import time, threading

class test():
    def __init__(self):
        print "init"
        self.stop_event = threading.Event()
        self.thread = threading.Thread(target = self.startThread)
        self.thread.start()

    def startThread(self):
        print "thread start"
        while not self.stop_event.isSet():
            time.sleep(1)
            print "thread running"

    def close(self):
        # Request thread to stop.
        self.stop_event.set()
        # Wait for thread to exit.
        self.thread.join()

    def __enter__(self):
        # Nothing to do, thread already started.  Could start
        # thread here to enforce use of context manager.

    def __exit__(self, exc_type, exc_value, traceback):
        self.close()

然后可以在上下文管理器中这样使用test()类:

with test():
    # Thread will be active here.
    pass

# Thread will have been stopped and joined.

或者,使用Python的contextlib.closing辅助函数,这样可以确保在退出时调用close

import contextlib

with contextlib.closing(test()):
    # Thread will be active here.
# But not here
3

__del__ 这个方法不会被调用,只要还有地方在引用 self,而在后台线程中就有这样的引用:在 def startThread(self):self 参数里。

你需要把运行后台线程的函数移到类外面。而不是使用 __del__ 方法,我建议你可以使用一个弱引用(weakref),像下面这样。这个代码应该可以在不使用 __del__() 方法和不需要 self.running 属性的情况下正常工作。

self.thread = threading.Thread(target=run_me, args=(weakref.ref(self),))
...
def run_me(weak_self):
    "Global function"
    while True:
        self = weak_self()
        if self is None: break    # no more reference to self
        ...
        del self        # forget this reference for now
        time.sleep(1)

撰写回答