关键词:python2.7 multiprocessing logger 单例
前言
前言,很多人提起python就说性能问题,此类文章网上一搜也是铺天盖地。前两天和公司一个老前辈闲谈的时候,他说了一句话让我感触很深,其实任何代码都会有性能问题,只不过一个人写的可能是在1000并发的时候有问题,而另一个是在1200的时候有问题,深感其然。
于此,对于想深究学习Python性能为什么会有问题的同学,提供一篇文章,是国内童鞋翻译的,讲的很不错。http://www.oschina.net/translate/pythons-hardest-problem
性能问题,在这篇文章里不多做赘述,简述一下为什么选择多进程而不是多线程。多线程共享进程的内存,本人需要实现的功能需要避免资源竞争,用多线程的话就必须要实现队列或者加锁。所以,多进程无可厚非。
进程池
#coding: utf-8
import multiprocessing
import time
def 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 multiprocessing
import os
import time
def 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系统使用)。我们来看下打印的结果:
可以清楚的看到,程序启动的时候创建了一个主进程ID为9133,之后创建了两个子进程9134和9135。任何一个进程执行完了func之后,都会再次领取任务。直到10个任务都执行完毕,进程结束释放。所以,进程池一旦创建好了一定数目的进程,是不会在额外创建进程的。
单例模式的logger
很多人可能要有疑问,logger根据官网的内容初始化就好了,和单例有什么关系。楼主是从.net,java过来的,对于整个系统在用,但是每次用都要初始化,而且每次初始化还都是一样内容的东西,不做个单例心里很不爽。从字面上来讲单例就是只有一个实例化对象,大家无论谁来调用,都是同一个对象。那么好处也就是减少了内存的消耗(其实就这一个类即使实例化100次占用的内存也是可以忽略的,但是在很大的项目里,很多这样的类的时候还是比较可观的,养成一个好习惯还是很必要的)。
import logging
import os
from logging.handlers import TimedRotatingFileHandler
def singleton(cls, *args, **kw):
instances = {}
def _singleton():
if cls not in instances:
instances[cls] = cls(*args, **kw)
return instances[cls]
return _singleton
@singleton
class 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,然后程序就无法匹配,自然无法删除需要删除的日志。
谢谢观赏