Сборщик мусора синглтона с демонической нитью - PullRequest
0 голосов
/ 17 марта 2020

Я реализовал регистратор в виде одиночного файла, в котором все сообщения go помещаются в очередь, и поток демона собирает эти сообщения из очереди и печатает их. Причина, по которой я использовал поток демона, заключается в том, что мне не нужно явно закрывать регистратор, как только я закончу (или приложение завершится). Я ожидал, что после удаления регистратора (сборщиком мусора) при закрытии приложения будет запущен метод __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)

1 Ответ

1 голос
/ 18 марта 2020

G C никогда не будет восстанавливать ваш экземпляр Logger, пока любая «живая» переменная в программе все еще содержит ссылку на него. Аргумент self метода listen(self):... является одной из таких переменных, и это метод верхнего уровня, в котором работает поток демона.

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

G C не может вернуть Logger экземпляр, пока поток демона не вернется из _listen(). Для этого есть только один способ:

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

Если вы «закроете» свой регистратор, то поток демона в итоге получит None из очереди, он вернется из вызова _listen(), и поток демона закончится. Но вы сказали

Причина, по которой я использовал поток демона, заключается в том, что мне не нужно явно закрывать регистратор.

Если вы не закроете регистратор , тогда поток демона никогда не закончится, аргумент self в _listen(self) никогда не выйдет за пределы go, а экземпляр Logger никогда не будет возвращен.

...