为了账号安全,请及时绑定邮箱和手机立即绑定

Python 多进程(进程池)及单例的logger模块

标签:
Java Python

关键词:python2.7 multiprocessing logger 单例

前言

前言,很多人提起python就说性能问题,此类文章网上一搜也是铺天盖地。前两天和公司一个老前辈闲谈的时候,他说了一句话让我感触很深,其实任何代码都会有性能问题,只不过一个人写的可能是在1000并发的时候有问题,而另一个是在1200的时候有问题,深感其然。
于此,对于想深究学习Python性能为什么会有问题的同学,提供一篇文章,是国内童鞋翻译的,讲的很不错。http://www.oschina.net/translate/pythons-hardest-problem
性能问题,在这篇文章里不多做赘述,简述一下为什么选择多进程而不是多线程。多线程共享进程的内存,本人需要实现的功能需要避免资源竞争,用多线程的话就必须要实现队列或者加锁。所以,多进程无可厚非。

进程池

本demo引用http://www.cnblogs.com/kaituorensheng/p/4465768.html

#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


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消