首页 热点资讯 义务教育 高等教育 出国留学 考研考公
您的当前位置:首页正文

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

2024-12-16 来源:花图问答

关键词:python2.7 multiprocessing logger 单例

前言

进程池

#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系统使用)。我们来看下打印的结果:

Paste_Image.png

可以清楚的看到,程序启动的时候创建了一个主进程ID为9133,之后创建了两个子进程9134和9135。任何一个进程执行完了func之后,都会再次领取任务。直到10个任务都执行完毕,进程结束释放。所以,进程池一旦创建好了一定数目的进程,是不会在额外创建进程的。

单例模式的logger

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.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,然后程序就无法匹配,自然无法删除需要删除的日志。

谢谢观赏

显示全文