关键词:python2.7 multiprocessing logger 单例
前言
前言,很多人提起python就说性能问题,此类文章网上一搜也是铺天盖地。前两天和公司一个老前辈闲谈的时候,他说了一句话让我感触很深,其实任何代码都会有性能问题,只不过一个人写的可能是在1000并发的时候有问题,而另一个是在1200的时候有问题,深感其然。
于此,对于想深究学习Python性能为什么会有问题的同学,提供一篇文章,是国内童鞋翻译的,讲的很不错。http://www.oschina.net/translate/pythons-hardest-problem
性能问题,在这篇文章里不多做赘述,简述一下为什么选择多进程而不是多线程。多线程共享进程的内存,本人需要实现的功能需要避免资源竞争,用多线程的话就必须要实现队列或者加锁。所以,多进程无可厚非。
进程池
#coding: utf-8import multiprocessingimport timedef func(msg): print "msg:", msg time.sleep(3) print "end"if __name__ == "__main__": pool = multiprocessing.Pool(processes = 3) for i in xrange(4): msg = "hello %d" %(i) pool.apply_async(func, (msg, )) #维持执行的进程总数为processes,当一个进程执行完毕后会添加新的进程进去 print "Mark~ Mark~ Mark~~~~~~~~~~~~~~~~~~~~~~" pool.close() pool.join() #调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束 print "Sub-process(es) done."
本人基于这个demo扩展,源代码目前不方便展示,后续空闲了会放到github。这里展示的时候还是贴上原作者的代码,以示尊重。注释写的很完善,但是有点描述不清晰。添加新的进程进去 会让人误以为是新创建了一个进程。于是我们在这个基础上稍作改动,来确认一下。
# -*- coding:utf-8 -*-import multiprocessingimport osimport timedef func(msg): print ('current process ID:', os.getpid()) print ('current parent ID:', os.getppid()) time.sleep(3)if __name__ == "__main__": pool = multiprocessing.Pool(processes=2) for i in xrange(10): msg = "hello %d" % (i) pool.apply_async(func, (msg,)) pool.close() pool.join() print "Sub-process(es) done."
这里我把原来的打印都去掉了,进程个数改为了2,for循环改为了10,在func里面打印当前子进程及其父进程的ID(os.getpid()函数只能在linux系统使用)。我们来看下打印的结果:
Paste_Image.png
可以清楚的看到,程序启动的时候创建了一个主进程ID为9133,之后创建了两个子进程9134和9135。任何一个进程执行完了func之后,都会再次领取任务。直到10个任务都执行完毕,进程结束释放。所以,进程池一旦创建好了一定数目的进程,是不会在额外创建进程的。
单例模式的logger
很多人可能要有疑问,logger根据官网的内容初始化就好了,和单例有什么关系。楼主是从.net,java过来的,对于整个系统在用,但是每次用都要初始化,而且每次初始化还都是一样内容的东西,不做个单例心里很不爽。从字面上来讲单例就是只有一个实例化对象,大家无论谁来调用,都是同一个对象。那么好处也就是减少了内存的消耗(其实就这一个类即使实例化100次占用的内存也是可以忽略的,但是在很大的项目里,很多这样的类的时候还是比较可观的,养成一个好习惯还是很必要的)。
import loggingimport osfrom logging.handlers import TimedRotatingFileHandlerdef singleton(cls, *args, **kw): instances = {} def _singleton(): if cls not in instances: instances[cls] = cls(*args, **kw) return instances[cls] return _singleton@singletonclass LogHelper(object): logfile = os.path.join(os.getcwd() + '/', 'log/log.log') if not os.path.exists(os.path.dirname(logfile)): os.makedirs(os.path.dirname(logfile)) logger = logging.getLogger() logger.setLevel(logging.ERROR) file_handler = TimedRotatingFileHandler(logfile, 'midnight', backupCount=15) file_handler.setFormatter( logging.Formatter('%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]')) # file_handler.suffix = "%Y%m%d.log" logger.addHandler(file_handler)
注意这里注释了一行代码,# file_handler.suffix = "%Y%m%d.log"。很多文章都会写,那这句话为什么不能写呢?我们进这个类TimedRotatingFileHandler 看一下init函数,有一段如下:
if self.when == 'S': self.interval = 1 # one second self.suffix = "%Y-%m-%d_%H-%M-%S" self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}(\.\w+)?$" elif self.when == 'M': self.interval = 60 # one minute self.suffix = "%Y-%m-%d_%H-%M" self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}(\.\w+)?$" elif self.when == 'H': self.interval = 60 * 60 # one hour self.suffix = "%Y-%m-%d_%H" self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}(\.\w+)?$" elif self.when == 'D' or self.when == 'MIDNIGHT': self.interval = 60 * 60 * 24 # one day self.suffix = "%Y-%m-%d" self.extMatch = r"^\d{4}-\d{2}-\d{2}(\.\w+)?$" elif self.when.startswith('W'): self.interval = 60 * 60 * 24 * 7 # one week if len(self.when) != 2: raise ValueError("You must specify a day for weekly rollover from 0 to 6 (0 is Monday): %s" % self.when) if self.when[1] < '0' or self.when[1] > '6': raise ValueError("Invalid day specified for weekly rollover: %s" % self.when) self.dayOfWeek = int(self.when[1]) self.suffix = "%Y-%m-%d" self.extMatch = r"^\d{4}-\d{2}-\d{2}(\.\w+)?$" else: raise ValueError("Invalid rollover interval specified: %s" % self.when) self.extMatch = re.compile(self.extMatch, re.ASCI
也就是说,根据你选的when ,会自动给suffix赋值。假如那行注释代码和这里面赋值的一样,是不是冗余了?假如不一样,问题就严重了,TimedRotatingFileHandler模块的作用就是把日志按照指定的日期分片,然后根据指定的备份数据自动维护日志的个数,在不一样的情况下你会发现删除历史日志文件的功能完全就废掉了。我们来看一下自动获取需要被删除日志的逻辑
def getFilesToDelete(self): """ Determine the files to delete when rolling over. More specific than the earlier method, which just used glob.glob(). """ dirName, baseName = os.path.split(self.baseFilename) fileNames = os.listdir(dirName) result = [] prefix = baseName + "." plen = len(prefix) for fileName in fileNames: if fileName[:plen] == prefix: suffix = fileName[plen:] if self.extMatch.match(suffix): result.append(os.path.join(dirName, fileName)) result.sort() if len(result) < self.backupCount: result = [] else: result = result[:len(result) - self.backupCount] return result
这段代码就是把目录下的文件名都读取出来,然后用正则匹配。那么正则哪里来的?就在上一段代码的最后一句。于是我们就知道了,匹配的正则是根据suffix来的,你自作聪明改了一个suffix,然后程序就无法匹配,自然无法删除需要删除的日志。
谢谢观赏
作者:_青菜_
链接:https://www.jianshu.com/p/eeda9fdf2e03