Python OOP版併發運行Linux命令:進程池控制多進程數量,隊列Queue實現讀寫進程間通信

第1小結是該類的定義。具體使用方法,請參考本文第2小結。


使用方法簡單,只需要import引用,定義好
1)單個id可運行的自定義函數,返回需要的結果,或者直接函數內部做IO,返回空字符串或者返回id;建議用絕對路徑;
2)id列表文件
3)定義好輸出日誌,裏面記錄着id和自定義函數的返回值。

難點:自定義函數的定義。
易錯點:自定義函數是否需要臨時文件夾?如果需要,還需要繼續編碼解決該問題,否則還會混亂。
 

1. 自定義類 ParallelMe.py

# define a class: ParallelMe[Run on Linux only!]
# version: 0.2.1
import subprocess
import time,multiprocessing,os,re,random,datetime

class ParallelMe(object):    
    #初始化屬性
    def __init__(self, doLinuxCMD_fn, id_list_file, core=3, hint_n=5,log_file_name='logs.txt',uniqLogName=False):
        """
        待批量處理的函數 doLinuxCMD_fn 僅僅依賴一個id,然後id的list在文件 id_list_file 中提供。
        if hint_n定義幾次,就出幾次進度提示,默認提示5次,最小是1次;
        uniqLogName==True時,會對log文件加時間戳後綴,默認不加時間戳;
        """
        # 必選值
        self.doLinuxCMD=doLinuxCMD_fn
        self.id_list_file=id_list_file
        #默認值
        self.core=core #使用的CPU邏輯核心數。該數字 x linux命令使用的線程數 要小於硬件CPU邏輯核心數
        self.hint_n=hint_n;
        self.log_file_name=log_file_name; #輸出日誌的文件名,內容是: id號 運行狀態(0表示正常,否則表示異常) 
        #需要處理
        #self.uniqLogName=uniqLogName;#這個文件名要加上時間戳,防止忘了修改日誌文件名而被覆蓋掉
        if uniqLogName:
            timsString=time.strftime("%Y%m%d-%H%M%S", time.localtime()) 
            self.log_file_name=log_file_name + timsString
        #
        #self.q=Queue(core+5) #創建隊列
        self.queue=multiprocessing.Manager().Queue(core+5);
    
    #定義worker: 讀和處理數據,並行
    def worker(self, cb):
        #print("worker===> ",cb, os.getpid() );
        #一個很耗時的計算
        rs=str(cb)+"\t"+str( self.doLinuxCMD(cb) ) #part2 中定義的
        self.queue.put(rs)   #結果輸出到管道
    
    #保存的線程1個
    def writer(self,log_file_name,ID_total,hint_n=10):
        hit_n=int(hint_n);
        if hint_n<1:
            hint_n=1;
        breaks=int(ID_total/hint_n) #顯示hint_n次進度提示
        i=0
        with open(log_file_name, 'w') as f: #這裏不能是變量名?
            while True:
                if i%breaks==0 or i==ID_total: #進度條
                    print(i," items processed in ", round(time.time()-self.start, 2)," seconds",sep="")
                # 如果所有條目都保存過了,則退出死循環                
                if(i==ID_total):
                    break;
                i+=1
                rs=self.queue.get() #waite while queue is empty
                f.write(rs+"\n") #寫入文件
                f.flush() #刷新緩存,一次性輸出到文件
    
    #主進程: 向進程池中提交任務,交給並行的worker()來處理
    def main(self):
        #1. 聲明進程池對象
        pool=multiprocessing.Pool(self.core)
        #2. 讀取id_list_file文件,分配任務給進程
        fr=open(self.id_list_file,'r')
        lines=fr.readlines();
        ID_total=len(lines);
        for lineR in lines:
            line=lineR.strip()
            arr=re.split(' ',line) ##print("start new process", line) #任務是一次發送完的
            pool.apply_async( self.worker, args=(arr[0],) )
        fr.close() #關閉文件

        #3. 分完任務,開始啓動保存進程,由writer()函數來處理
        pOut = multiprocessing.Process(target=self.writer, args=(self.log_file_name,ID_total,self.hint_n,)) # args:元組參數,如果參數就一個,記得加逗號’,’
        pOut.start()

        #4. 等待讀進程worker()全部結束
        pool.close()
        pool.join()
        #5. 等待寫循環結束
        pOut.join()
    
    #運行
    def run(self):
        #輸出運行參數
        self.start=time.time();#啓動時的時間
        print("function name:", self.doLinuxCMD);
        print("id list file:", self.id_list_file);
        print('CPU core number:', self.core);
        print('hint number:', self.hint_n);
        print('log_file_name:', self.log_file_name);
        #
        print("#"*40,'\n',datetime.datetime.now(),"\n","#"*40, sep="")
        print('='*10, ">Begin of main process[", os.getpid(), "][child pid by parent ppid]", sep="")
        self.main(); #開啓多進程
        print(time.time()-self.start,'s <', '='*10, "End of main process[", os.getpid(),']', sep="")
        print("#"*40,"\nLog file: "+ os.getcwd()+"/"+self.log_file_name,"\n","#"*40, sep="")
# end of class

 

2. 測試 demo.py

from ParallelMe import ParallelMe
################################
#使用三部曲
################################
import os,subprocess,random

##part 1 定義路徑
print('Step1> define path: ',os.getcwd())
os.chdir('/home/wangjl/test') #定義工作目錄,僅對python有效。對linux命令建議都使用絕對路徑。


#part 2 定義linux命令,返回字符串,會被記錄到日誌文件中。
print("Step2> define the function to be run parallelly: doLinuxCMD(id)")
#目的:需要平行處理的linux命令。if the function can run on one id, it can run on a list of ids.
#要點: 使用id拼接linux命令。建議都用絕對路徑。
def doLinuxCMD(id):
    #構建命令,很複雜的linux命令
    cmd="sleep "+str(random.randint(0,4)); #這個linux命令爲休眠一段時間。可以是linux腳本,有輸入和輸出,建議用絕對路徑。
    #執行linux命令
    (status, output)=subprocess.getstatusoutput(cmd)
    #print(output) #查看linux命令輸出到屏幕上的文字
    rs=str(status)+"\t"+output# +"\t"+str(os.getpid())+"\t"+str(os.getppid());
    return rs #返回狀態碼status,0表示命令正常執行,其他表示異常,需要查看output推測具體原因
#test
#doLinuxCMD(1) #status output pid ppid


#part 3 批量運行該linux命令
print("Step3> run the function parallelly");
#doLinuxCMD爲函數,要有str返回值
#id_list爲id列表文本文件名,一個id一行。建議用絕對路徑
#core爲並行個數(默認是3),要小於CPU個數,但是超過id總個數也沒有意義;
id_list="/home/wangjl/test/a.txt"
myTasks=ParallelMe(doLinuxCMD, id_list, core=55);
myTasks.run()

 

3. 運行和輸出結果
id list就是一行一個id號即可
$ head /home/wangjl/test/a.txt
1
2
3
或者字符串。

$ python demo.py
Step1> define path:  /home/wangjl/test
Step2> define the function to be run parallelly: doLinuxCMD(id)
Step3> run the function parallelly
function name: <function doLinuxCMD at 0x7ffb11f8b1e0>
id list file: /home/wangjl/test/a.txt
CPU core number: 55
hint number: 5
log_file_name: logs.txt
########################################
2019-09-14 13:14:29.823499
########################################
==========>Begin of main process[381837][child pid by parent ppid]
0 items processed in 0.12 seconds
10 items processed in 1.09 seconds
20 items processed in 2.09 seconds
30 items processed in 2.12 seconds
40 items processed in 3.11 seconds
50 items processed in 4.13 seconds
4.265288829803467s <==========End of main process[381837]
########################################
Log file: /home/wangjl/test/logs.txt
########################################

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章