【问题标题】:Garbage collector of singleton with daemon thread带有守护线程的单例垃圾收集器
【发布时间】:2020-03-17 14:18:50
【问题描述】:

我已将记录器实现为单例,其中所有消息都进入队列,守护线程从队列中收集这些消息并打印它们。 我使用守护线程的原因是,一旦我完成它(或应用程序退出),我就不必显式关闭记录器。 我希望在应用程序关闭时(由垃圾收集器)删除记录器,然后 __del__ 方法将运行并在之后进行清理。我很惊讶事实并非如此。

当我将线程更改为非守护程序时,它工作得很好(显然我必须进行一些其他更改才能退出应用程序)。我想知道我做错了什么,或者这只是一个不好的做法。

这里附上代码:(我建议__del__函数之后的所有内容都没有意思)。

import os
import sys
import time
import Queue
import weakref
import datetime
import threading

class Logger(object):
    """
    Logger class implemented with a queue of messages, and supports only a single instace.
    This instance can be acquired by using the "GetLogger" method.
    """

    __instance = None

    @classmethod
    def GetLogger(cls, fpath, source_name=None):

        if cls.__instance is None:

            return Logger(fpath, source_name=source_name)

        else:
          if source_name is None:
              cls.__instance().log('%s@%s: %s\n' % (cls.current_date(), cls.current_time(), "Using existing Logger instance"), "REUSAGE")
          else:
              cls.__instance().log('%s@%s - %-17s: %s\n' % (cls.current_date(), cls.current_time(), source_name, "Using existing Logger instance"), "REUSAGE")

        return cls.__instance()

    def __init__(self, fpath, start_time = time.time(), source_name=None):

        if self.__instance is not None:

            raise ValueError("Singleton object already exists")

        self.__instance = weakref.ref(self)
        self.__start_time = start_time
        self.__queue = Queue.Queue()
        self.__listener = threading.Thread(target=self._listen)
        self.__listener.daemon = True
        self.__listener.start()
        if not os.path.exists(os.path.dirname(fpath)):
            os.makedirs(os.path.dirname(fpath))
        self.__f = open(fpath, 'a')
        if source_name is None:
            self.log('%s@%s: %s\n' % (self.current_date(), self.current_time(), "Created a new Logger instance"), "CREATION")
        else:
            self.log('%s@%s - %-17s: %s\n' % (self.current_date(), self.current_time(), source_name, "Created a new Logger instance"), "CREATION")

    @staticmethod
    def current_date():
        return str(datetime.datetime.now().date().isoformat())

    @staticmethod
    def current_time():
        return str(datetime.datetime.now().time().isoformat())

    def _listen(self):

        while True:

            msg = self.__queue.get()
            if msg is None:
                break

            self.__print_message(msg)

    def __print_message(self, msg_tup): # msg_tup = (message, level, stdout)
        msg_time = time.time()
        if msg_tup[2]:
            try:
                print(("|%013.6f|%-8s>>>%s" % (msg_time - self.__start_time, msg_tup[1], msg_tup[0]))),
                sys.stdout.flush()
            except:
                pass
        try:
            self.__f.write("|%013.6f|%-8s>>>%s" % (msg_time - self.__start_time, msg_tup[1], msg_tup[0]))
            self.__f.flush()
        except:
            pass

    def log(self, msg, level, to_stdout=True):
        self.__queue.put((msg, level, to_stdout))

    def close(self):
        self.__queue.put(None)
        self.__instance = None

    def __del__(self):

        while not self.__queue.empty():
            msg = self.__queue.get()
            if msg is not None:
                self.__print_message(msg)
        print("Dead...")
        self.__f.close()
        self.close()

    def info(self, msg, source_name=None, to_stdout=True):
        if source_name is None:
            self.log('%s@%s: %s\n' % (self.current_date(), self.current_time(), msg), "INFO", to_stdout=to_stdout)
        else:
            self.log('%s@%s - %-17s: %s\n' % (self.current_date(), self.current_time(), source_name, msg), "INFO", to_stdout=to_stdout)

    def debug(self, msg, source_name=None, to_stdout=True):
        if source_name is None:
            self.log('%s@%s: %s\n' %
                               (self.current_date(), self.current_time(), msg), "DEBUG", to_stdout=to_stdout)
        else:
            self.log('%s@%s - %-17s: %s\n' % (self.current_date(), self.current_time(), source_name, msg), "DEBUG", to_stdout=to_stdout)

    def trace(self, msg, sdource_name=None, to_stdout=True):
        if source_name is None:
            self.log('%s@%s: %s\n' %
                               (self.current_date(), self.current_time(), msg), "TRACE", to_stdout=to_stdout)
        else:
            self.log('%s@%s - %-17s: %s\n' % (self.current_date(), self.current_time(), source_name, msg), "TRACE", to_stdout=to_stdout)

    def warn(self, msg, source_name=None, to_stdout=True):
        if source_name is None:
            self.log('%s@%s: %s\n' %
                               (self.current_date(), self.current_time(), msg), "WARN", to_stdout=to_stdout)
        else:
            self.log('%s@%s - %-17s: %s\n' % (self.current_date(), self.current_time(), source_name, msg), "WARN", to_stdout=to_stdout)

    def error(self, msg, source_name=None, to_stdout=True):
        if source_name is None:
            self.log('%s@%s: %s\n' %
                               (self.current_date(), self.current_time(), msg), "ERROR", to_stdout=to_stdout)
        else:
            self.log('%s@%s - %-17s: %s\n' % (self.current_date(), self.current_time(), source_name, msg), "ERROR", to_stdout=to_stdout)

    def critical(self, msg, source_name=None, to_stdout=True):
        if source_name is None:
            self.log('%s@%s: %s\n' %
                               (self.current_date(), self.current_time(), msg), "CRITICAL", to_stdout=to_stdout)
        else:
            self.log('%s@%s - %-17s: %s\n' % (self.current_date(), self.current_time(), source_name, msg), "CRITICAL", to_stdout=to_stdout)

【问题讨论】:

  • 您期望 GC 在程序终止之前回收您的 Logger 实例的原因是什么?守护线程应该一直运行到最后,并且线程的主例程_listen() 始终通过其self 参数对对象进行实时引用。
  • 基本上我的主张是垃圾收集器即使在程序结束后也会删除对象。例如:class X(object): def __init__(self): print "Created..." def __del__(self): print "Destroyed..." if __name__ == '__main__': x = X() 这可能是错误的,但如果是这样,为什么认为守护线程在完成后被垃圾回收是错误的?有没有另一种实现记录器的方法,它在不同的线程上输出,而不必担心关闭它?
  • 这取决于“程序结束后”的含义。当上面示例中的主线程在脚本末尾运行时,您的 class X 实例被删除,我并不感到惊讶。但这与运行while True:... 循环的守护线程不同。 class X 对象被释放,因为主线程离开了变量x 的范围。但是,您的原始问题中的守护线程何时会离开您的 __listen(self) 方法中的 self 参数的范围?我希望在此之前终止该过程。

标签: python multithreading garbage-collection singleton daemon


【解决方案1】:

只要程序中的任何“活动”变量仍然包含对它的引用,GC 就永远不会回收您的 Logger 实例。 listen(self):... 方法的 self 参数就是这样一个变量,它是运行守护线程的顶级方法。

def _listen(self):
    while True:
        msg = self.__queue.get()
        if msg is None:
            break
        self.__print_message(msg)

在守护线程从_listen() 返回之前,GC 无法回收Logger 实例。只有一种方法可以做到这一点:

def close(self):
    self.__queue.put(None)

如果您“关闭”您的记录器,那么守护线程最终将从队列中获取None,它将从_listen() 调用返回,并且守护线程将结束。但是,你说

我使用守护线程的原因是我不必显式关闭记录器。

如果你不关闭记录器,那么守护线程永远不会结束,_listen(self) 中的 self arg 永远不会超出范围,Logger 实例永远不会被回收。

【讨论】:

  • 我相信这回答了我的问题。您是否知道不需要显式关闭记录器(不使用守护线程)的任何其他方式?最好为 IO 使用单独的线程。
  • 您在原始问题中说“...当应用程序关闭时...”。我对您的应用程序了解得不够多,无法知道应用程序“关闭”或它如何“关闭”的所有原因。如果其他进程有可能显式终止您的应用程序(例如,在类似 unix 的操作系统中使用 kill -KILL 信号),那么任何进程都无法以任何语言“处理”它。对于任何其他类型的关闭,......对不起,但我并不是真正的 Python 大师:我不知道做很多事情的“pythonic”方式。
猜你喜欢
  • 2011-10-15
  • 2011-01-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多