【发布时间】: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