Python 多進(jìn)程(進(jìn)程池)及單例的logger模塊

關(guān)鍵詞:python2.7 multiprocessing logger 單例

前言

前言,很多人提起python就說性能問題,此類文章網(wǎng)上一搜也是鋪天蓋地。前兩天和公司一個老前輩閑談的時候,他說了一句話讓我感觸很深,其實任何代碼都會有性能問題,只不過一個人寫的可能是在1000并發(fā)的時候有問題,而另一個是在1200的時候有問題,深感其然。
于此,對于想深究學(xué)習(xí)Python性能為什么會有問題的同學(xué),提供一篇文章,是國內(nèi)童鞋翻譯的,講的很不錯。http://www.oschina.net/translate/pythons-hardest-problem
性能問題,在這篇文章里不多做贅述,簡述一下為什么選擇多進(jìn)程而不是多線程。多線程共享進(jìn)程的內(nèi)存,本人需要實現(xiàn)的功能需要避免資源競爭,用多線程的話就必須要實現(xiàn)隊列或者加鎖。所以,多進(jìn)程無可厚非。

進(jìn)程池

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

#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, ))   #維持執(zhí)行的進(jìn)程總數(shù)為processes,當(dāng)一個進(jìn)程執(zhí)行完畢后會添加新的進(jìn)程進(jìn)去

    print "Mark~ Mark~ Mark~~~~~~~~~~~~~~~~~~~~~~"
    pool.close()
    pool.join()   #調(diào)用join之前,先調(diào)用close函數(shù),否則會出錯。執(zhí)行完close后不會有新的進(jìn)程加入到pool,join函數(shù)等待所有子進(jìn)程結(jié)束
    print "Sub-process(es) done."

本人基于這個demo擴展,源代碼目前不方便展示,后續(xù)空閑了會放到github。這里展示的時候還是貼上原作者的代碼,以示尊重。注釋寫的很完善,但是有點描述不清晰。添加新的進(jìn)程進(jìn)去 會讓人誤以為是新創(chuàng)建了一個進(jìn)程。于是我們在這個基礎(chǔ)上稍作改動,來確認(rèn)一下。

# -*- 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."

這里我把原來的打印都去掉了,進(jìn)程個數(shù)改為了2,for循環(huán)改為了10,在func里面打印當(dāng)前子進(jìn)程及其父進(jìn)程的ID(os.getpid()函數(shù)只能在linux系統(tǒng)使用)。我們來看下打印的結(jié)果:

Paste_Image.png

可以清楚的看到,程序啟動的時候創(chuàng)建了一個主進(jìn)程ID為9133,之后創(chuàng)建了兩個子進(jìn)程9134和9135。任何一個進(jìn)程執(zhí)行完了func之后,都會再次領(lǐng)取任務(wù)。直到10個任務(wù)都執(zhí)行完畢,進(jìn)程結(jié)束釋放。所以,進(jìn)程池一旦創(chuàng)建好了一定數(shù)目的進(jìn)程,是不會在額外創(chuàng)建進(jìn)程的。

單例模式的logger

很多人可能要有疑問,logger根據(jù)官網(wǎng)的內(nèi)容初始化就好了,和單例有什么關(guān)系。樓主是從.net,java過來的,對于整個系統(tǒng)在用,但是每次用都要初始化,而且每次初始化還都是一樣內(nèi)容的東西,不做個單例心里很不爽。從字面上來講單例就是只有一個實例化對象,大家無論誰來調(diào)用,都是同一個對象。那么好處也就是減少了內(nèi)存的消耗(其實就這一個類即使實例化100次占用的內(nèi)存也是可以忽略的,但是在很大的項目里,很多這樣的類的時候還是比較可觀的,養(yǎng)成一個好習(xí)慣還是很必要的)。

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"。很多文章都會寫,那這句話為什么不能寫呢?我們進(jìn)這個類TimedRotatingFileHandler 看一下init函數(shù),有一段如下:

    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

也就是說,根據(jù)你選的when ,會自動給suffix賦值。假如那行注釋代碼和這里面賦值的一樣,是不是冗余了?假如不一樣,問題就嚴(yán)重了,TimedRotatingFileHandler模塊的作用就是把日志按照指定的日期分片,然后根據(jù)指定的備份數(shù)據(jù)自動維護(hù)日志的個數(shù),在不一樣的情況下你會發(fā)現(xiàn)刪除歷史日志文件的功能完全就廢掉了。我們來看一下自動獲取需要被刪除日志的邏輯

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

這段代碼就是把目錄下的文件名都讀取出來,然后用正則匹配。那么正則哪里來的?就在上一段代碼的最后一句。于是我們就知道了,匹配的正則是根據(jù)suffix來的,你自作聰明改了一個suffix,然后程序就無法匹配,自然無法刪除需要刪除的日志。

謝謝觀賞

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容