python 多進程編程
python中的多線程其實並不是真正的多線程,如果想要充分地使用多核CPU的資源,在python中大部分情況需要使用多進程。Python提供了非常好用的多進程包multiprocessing,只需要定義一個函數,Python會完成其他所有事情。藉助這個包,可以輕鬆完成從單進程到併發執行的轉換。multiprocessing支持子進程、通信和共享數據、執行不同形式的同步,提供了Process、Queue、Pipe、Lock等組件。
1、Process
Process構造函數主要有兩個參數,target表示要運行的函數名,args表示傳遞的參數。
方法:
is_alive()、join([timeout])、run()、start()、terminate()。
run和start的區別:
start()方法
開始線程活動。
對每一個線程對象來說它只能被調用一次,它安排對象在一個另外的單獨線程中調用run()方法(而非當前所處線程)。
當該方法在同一個線程對象中被調用超過一次時,會引入RuntimeError(運行時錯誤)。
run()方法
代表了線程活動的方法。
你可以在子類中重寫此方法。標準run()方法調用了傳遞給對象的構造函數的可調對象作爲目標參數,如果有這樣的參數的話,順序和關鍵字參數分別從args和kargs取得。
所以簡單地說,start就是另外開啓一個進程來運行這個函數,run就是在當前進程下開始這個函數。
import multiprocessing
def worker(value1, value2):
print(multiprocessing.current_process())
p1 = multiprocessing.Process(target=worker, args=(1, 2,))
p2 = multiprocessing.Process(target=worker, args=(3, 4,))
p1.run()
p2.start()
<_MainProcess(MainProcess, started)>
<Process(Process-2, started)>
daemon 後臺駐留程序
意思就是說,如果主進程運行結束,則結束子進程。
import multiprocessing
import time
def worker(value1, value2):
time.sleep(1)
print(multiprocessing.current_process())
p1 = multiprocessing.Process(target=worker, args=(1, 2,))
p2 = multiprocessing.Process(target=worker, args=(3, 4,))
p1.daemon = True
p2.daemon = True
p1.start()
p2.start()
print("end")
end
import multiprocessing
import time
def worker(value1, value2):
time.sleep(1)
print(multiprocessing.current_process())
p1 = multiprocessing.Process(target=worker, args=(1, 2,))
p2 = multiprocessing.Process(target=worker, args=(3, 4,))
p1.daemon = True
p2.daemon = True
p1.start()
p2.start()
p1.join()
p2.join()
print("end")
<Process(Process-1, started daemon)>
<Process(Process-2, started daemon)>
end
需要注意的是,如果調用的是run,則會正常輸出,因爲run是在當前進程中運行。
Lock
通過鎖來解決進程間數據同步問題。
可以通過兩種方式來使用Lock。
import multiprocessing
import time
def worker(lock, value2):
with lock:
print(lock)
print(value2)
lock.acquire()
print(lock)
print(value2)
lock.release()
lock = multiprocessing.Lock()
p1 = multiprocessing.Process(target=worker, args=(lock, 2,))
p2 = multiprocessing.Process(target=worker, args=(lock, 4,))
p1.run()
p2.run()
Semaphore
信號量,和PV原語使用方法是一致的,可以用來控制對共享資源的最大連接數。
import multiprocessing
import time
def worker(s, i):
s.acquire()
print(multiprocessing.current_process().name + "acquire")
time.sleep(i)
print(multiprocessing.current_process().name + "release\n")
s.release()
if __name__ == "__main__":
s = multiprocessing.Semaphore(2)
for i in range(5):
p = multiprocessing.Process(target = worker, args=(s, i*2))
p.start()
Process-1acquire
Process-1release
Process-2acquire
Process-3acquire
Process-2release
Process-4acquire
Process-3release
Process-5acquire
Process-4release
Process-5release
Queue 實現讀者寫者問題
Queue是多進程安全的隊列,可以使用Queue實現多進程之間的數據傳遞。put方法用以插入數據到隊列中,put方法還有兩個可選參數:blocked和timeout。如果blocked爲True(默認值),並且timeout爲正值,該方法會阻塞timeout指定的時間,直到該隊列有剩餘的空間。如果超時,會拋出Queue.Full異常。如果blocked爲False,但該Queue已滿,會立即拋出Queue.Full異常。
get方法可以從隊列讀取並且刪除一個元素。同樣,get方法有兩個可選參數:blocked和timeout。如果blocked爲True(默認值),並且timeout爲正值,那麼在等待時間內沒有取到任何元素,會拋出Queue.Empty異常。如果blocked爲False,有兩種情況存在,如果Queue有一個值可用,則立即返回該值,否則,如果隊列爲空,則立即拋出Queue.Empty異常。Queue的一段示例代碼:
import multiprocessing
import time
def writer_proc(q):
while True:
try:
q.put(1, block = False)
except:
pass
def reader_proc(q):
while True:
try:
print q.get(block = False)
except:
time.sleep(1)
pass
if __name__ == "__main__":
q = multiprocessing.Queue(3)
writer = multiprocessing.Process(target=writer_proc, args=(q,))
writer.start()
reader = multiprocessing.Process(target=reader_proc, args=(q,))
reader.start()
reader.join()
writer.join()
pipe 用於實現IPC(進程間通信)
Pipe方法返回(conn1, conn2)代表一個管道的兩個端。Pipe方法有duplex參數,如果duplex參數爲True(默認值),那麼這個管道是全雙工模式,也就是說conn1和conn2均可收發。duplex爲False,conn1只負責接受消息,conn2只負責發送消息。
send和recv方法分別是發送和接受消息的方法。例如,在全雙工模式下,可以調用conn1.send發送消息,conn1.recv接收消息。如果沒有消息可接收,recv方法會一直阻塞。如果管道已經被關閉,那麼recv方法會拋出EOFError。
import multiprocessing
import time
def proc1(pipe):
while True:
for i in xrange(10000):
print "send: %s" %(i)
pipe.send(i)
time.sleep(1)
def proc2(pipe):
while True:
print "proc2 rev:", pipe.recv()
time.sleep(1)
def proc3(pipe):
while True:
print "PROC3 rev:", pipe.recv()
time.sleep(1)
if __name__ == "__main__":
pipe = multiprocessing.Pipe()
p1 = multiprocessing.Process(target=proc1, args=(pipe[0],))
p2 = multiprocessing.Process(target=proc2, args=(pipe[1],))
#p3 = multiprocessing.Process(target=proc3, args=(pipe[1],))
p1.start()
p2.start()
#p3.start()
p1.join()
p2.join()
#p3.join()
Pool 進程池
在利用Python進行系統管理的時候,特別是同時操作多個文件目錄,或者遠程控制多臺主機,並行操作可以節約大量的時間。當被操作對象數目不大時,可以直接利用multiprocessing中的Process動態成生多個進程,十幾個還好,但如果是上百個,上千個目標,手動的去限制進程數量卻又太過繁瑣,此時可以發揮進程池的功效。
Pool可以提供指定數量的進程,供用戶調用,當有新的請求提交到pool中時,如果池還沒有滿,那麼就會創建一個新的進程用來執行該請求;但如果池中的進程數已經達到規定最大值,那麼該請求就會等待,直到池中有進程結束,纔會創建新的進程來它。
函數解釋:
- apply_async(func[, args[, kwds[, callback]]]) 它是非阻塞,apply(func[, args[, kwds]])是阻塞的(理解區別,看例1例2結果區別)
- close() 關閉pool,使其不在接受新的任務。
- terminate() 結束工作進程,不在處理未完成的任務。
- join() 主進程阻塞,等待子進程的退出, join方法要在close或terminate之後使用。
非阻塞
#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."
mMsg: hark~ Mark~ Mark~~~~~~~~~~~~~~~~~~~~~~ello 0
msg: hello 1
msg: hello 2
end
msg: hello 3
end
end
end
Sub-process(es) done.
就算是說,主進程不需要等待子進程運行完,主進程可以獨立完成自己的任務。
阻塞
#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(func, (msg, )) #維持執行的進程總數爲processes,當一個進程執行完畢後會添加新的進程進去
print "Mark~ Mark~ Mark~~~~~~~~~~~~~~~~~~~~~~"
pool.close()
pool.join() #調用join之前,先調用close函數,否則會出錯。執行完close後不會有新的進程加入到pool,join函數等待所有子進程結束
print "Sub-process(es) done."
msg: hello 0
end
msg: hello 1
end
msg: hello 2
end
msg: hello 3
end
Mark~ Mark~ Mark~~~~~~~~~~~~~~~~~~~~~~
Sub-process(es) done.
主進程必須等待子進程運行完才能夠繼續執行自己的代碼。
進程池中的進程間數據共享
import multiprocessing
import time
def worker(array, value):
with lock:
array.append(value)
def init(l):
global lock
lock = l
manager = multiprocessing.Manager()
globalArray = manager.list()
l = multiprocessing.Lock()
print(time.clock())
pool = multiprocessing.Pool(5, initializer=init, initargs=(l,))
for i in range(10):
pool.apply_async(worker, args=(globalArray, i,))
pool.close()
pool.join()
print(len(globalArray))
值得注意的是,manager中是需要加鎖的,具體原因如下:
import multiprocessing
import time
def worker(array, value):
with lock:
time.sleep(1 - value / 10)
array.append(value)
def init(l):
global lock
lock = l
manager = multiprocessing.Manager()
globalArray = manager.list()
l = multiprocessing.Lock()
pool = multiprocessing.Pool(5, initializer=init, initargs=(l,))
for i in range(10):
pool.apply_async(worker, args=(globalArray, i,))
pool.close()
pool.join()
print(globalArray)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
import multiprocessing
import time
def worker(array, value):
time.sleep(1 - value / 10)
array.append(value)
def init(l):
global lock
lock = l
manager = multiprocessing.Manager()
globalArray = manager.list()
l = multiprocessing.Lock()
pool = multiprocessing.Pool(5, initializer=init, initargs=(l,))
for i in range(10):
pool.apply_async(worker, args=(globalArray, i,))
pool.close()
pool.join()
print(globalArray)
[1, 0, 2, 3, 4, 5, 6, 7, 8, 9]
可以發現兩種做法的結果是不一樣的,所以是需要進行加鎖的。
文中部分內容引用python多進程編程