最近接觸一個專案,要在多個虛擬機器中執行任務,參考別人之前專案的程式碼,採用了多程式來處理,於是上網查了查python中的多程式
一、先說說Queue(佇列物件)
Queue是python中的標準庫,可以直接import 引用,之前學習的時候有聽過著名的“先吃先拉”與“後吃先吐”,其實就是這裡說的佇列,佇列的構造的時候可以定義它的容量,別吃撐了,吃多了,就會報錯,構造的時候不寫或者寫個小於1的數則表示無限多
import Queue
q = Queue.Queue(10)
向佇列中放值(put)
q.put(‘yang’)
q.put(4)
q.put([‘yan’,’xing’])
在佇列中取值get()
預設的佇列是先進先出的
>>> q.get()
‘yang’
>>> q.get()
4
>>> q.get()
[‘yan’, ‘xing’]
當一個佇列為空的時候如果再用get取則會堵塞,所以取佇列的時候一般是用到
get_nowait()方法,這種方法在向一個空佇列取值的時候會拋一個Empty異常
所以更常用的方法是先判斷一個佇列是否為空,如果不為空則取值
佇列中常用的方法
Queue.qsize() 返回佇列的大小
Queue.empty() 如果佇列為空,返回True,反之False
Queue.full() 如果佇列滿了,返回True,反之False
Queue.get([block[, timeout]]) 獲取佇列,timeout等待時間
Queue.get_nowait() 相當Queue.get(False)
非阻塞 Queue.put(item) 寫入佇列,timeout等待時間
Queue.put_nowait(item) 相當Queue.put(item, False)
二、multiprocessing中使用子程式概念
from multiprocessing import Process
可以通過Process來構造一個子程式
p = Process(target=fun,args=(args))
再通過p.start()來啟動子程式
再通過p.join()方法來使得子程式執行結束後再執行父程式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
from multiprocessing import Process import os # 子程式要執行的程式碼 def run_proc(name): print 'Run child process %s (%s)...' % (name, os.getpid()) if __name__=='__main__': print 'Parent process %s.' % os.getpid() p = Process(target=run_proc, args=('test',)) print 'Process will start.' p.start() p.join() print 'Process end.' |
三、在multiprocessing中使用pool
如果需要多個子程式時可以考慮使用程式池(pool)來管理
1 |
from multiprocessing import Pool |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
from multiprocessing import Pool import os, time def long_time_task(name): print 'Run task %s (%s)...' % (name, os.getpid()) start = time.time() time.sleep(3) end = time.time() print 'Task %s runs %0.2f seconds.' % (name, (end - start)) if __name__=='__main__': print 'Parent process %s.' % os.getpid() p = Pool() for i in range(5): p.apply_async(long_time_task, args=(i,)) print 'Waiting for all subprocesses done...' p.close() p.join() print 'All subprocesses done.' |
pool建立子程式的方法與Process不同,是通過
p.apply_async(func,args=(args))實現,一個池子裡能同時執行的任務是取決你電腦的cpu數量,如我的電腦現在是有4個cpu,那會子程式task0,task1,task2,task3可以同時啟動,task4則在之前的一個某個程式結束後才開始
上面的程式執行後的結果其實是按照上圖中1,2,3分開進行的,先列印1,3秒後列印2,再3秒後列印3
程式碼中的p.close()是關掉程式池子,是不再向裡面新增程式了,對Pool
物件呼叫join()
方法會等待所有子程式執行完畢,呼叫join()
之前必須先呼叫close()
,呼叫close()
之後就不能繼續新增新的Process
了。
當時也可以是例項pool的時候給它定義一個程式的多少
如果上面的程式碼中p=Pool(5)那麼所有的子程式就可以同時進行
三、多個子程式間的通訊
多個子程式間的通訊就要採用第一步中說到的Queue,比如有以下的需求,一個子程式向佇列中寫資料,另外一個程式從佇列中取資料,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
#coding:gbk from multiprocessing import Process, Queue import os, time, random # 寫資料程式執行的程式碼: def write(q): for value in ['A', 'B', 'C']: print 'Put %s to queue...' % value q.put(value) time.sleep(random.random()) # 讀資料程式執行的程式碼: def read(q): while True: if not q.empty(): value = q.get(True) print 'Get %s from queue.' % value time.sleep(random.random()) else: break if __name__=='__main__': # 父程式建立Queue,並傳給各個子程式: q = Queue() pw = Process(target=write, args=(q,)) pr = Process(target=read, args=(q,)) # 啟動子程式pw,寫入: pw.start() # 等待pw結束: pw.join() # 啟動子程式pr,讀取: pr.start() pr.join() # pr程式裡是死迴圈,無法等待其結束,只能強行終止: print print '所有資料都寫入並且讀完' |
四、關於上面程式碼的幾個有趣的問題
1 2 3 4 5 6 7 8 9 10 11 |
if __name__=='__main__': # 父程式建立Queue,並傳給各個子程式: q = Queue() p = Pool() pw = p.apply_async(write,args=(q,)) pr = p.apply_async(read,args=(q,)) p.close() p.join() print print '所有資料都寫入並且讀完' |
如果main函式寫成上面的樣本,本來我想要的是將會得到一個佇列,將其作為引數傳入程式池子裡的每個子程式,但是卻得到
RuntimeError: Queue objects should only be shared between processes through inheritance
的錯誤,查了下,大意是佇列物件不能在父程式與子程式間通訊,這個如果想要使用程式池中使用佇列則要使用multiprocess的Manager類
1 2 3 4 5 6 7 8 9 10 11 12 13 |
if __name__=='__main__': manager = multiprocessing.Manager() # 父程式建立Queue,並傳給各個子程式: q = manager.Queue() p = Pool() pw = p.apply_async(write,args=(q,)) time.sleep(0.5) pr = p.apply_async(read,args=(q,)) p.close() p.join() print print '所有資料都寫入並且讀完' |
這樣這個佇列物件就可以在父程式與子程式間通訊,不用池則不需要Manager,以後再擴充套件multiprocess中的Manager類吧
關於鎖的應用,在不同程式間如果有同時對同一個佇列操作的時候,為了避免錯誤,可以在某個函式操作佇列的時候給它加把鎖,這樣在同一個時間內則只能有一個子程式對佇列進行操作,鎖也要在manager物件中的鎖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
#coding:gbk from multiprocessing import Process,Queue,Pool import multiprocessing import os, time, random # 寫資料程式執行的程式碼: def write(q,lock): lock.acquire() #加上鎖 for value in ['A', 'B', 'C']: print 'Put %s to queue...' % value q.put(value) lock.release() #釋放鎖 # 讀資料程式執行的程式碼: def read(q): while True: if not q.empty(): value = q.get(False) print 'Get %s from queue.' % value time.sleep(random.random()) else: break if __name__=='__main__': manager = multiprocessing.Manager() # 父程式建立Queue,並傳給各個子程式: q = manager.Queue() lock = manager.Lock() #初始化一把鎖 p = Pool() pw = p.apply_async(write,args=(q,lock)) pr = p.apply_async(read,args=(q,)) p.close() p.join() print print '所有資料都寫入並且讀完' |
參考文章: