带有守护线程的单例垃圾收集器

Garbage collector of singleton with daemon thread

我已经将记录器实现为单例,其中所有消息都进入队列,守护线程从队列中收集这些消息并打印出来。 我使用守护线程的原因是我不必在完成记录器(或应用程序退出)后显式关闭记录器。 我预计一旦应用程序关闭时记录器被删除(由垃圾收集器),然后 __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 实例,只要程序中的任何 "live" 变量仍然包含对它的引用。 listen(self):... 方法的 self 参数就是这样一个变量,这是守护线程是 运行.

的顶级方法
def _listen(self):
    while True:
        msg = self.__queue.get()
        if msg is None:
            break
        self.__print_message(msg)

GC 无法回收 Logger 实例,直到守护线程 return 来自 _listen()。只有一种方法可以实现:

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

如果您 "close" 您的记录器,那么守护线程最终将从队列中获取 None,它将 return 来自 _listen() 调用,并且守护线程线程将结束。但是,你说

The reason I used a daemon thread is so I don't have to explicitly close the logger.

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