Python multiprocessing使用詳解

Python multiprocessing使用詳解

multiprocessing包是Python中的多進程管理包。
與threading.Thread類似,它可以利用multiprocessing.Process對象來創建一個進程。
該進程可以運行在Python程序內部編寫的函數。
該Process對象與Thread對象的用法相同,也有start(), run(), join()的方法。
此外multiprocessing包中也有Lock/Event/Semaphore/Condition類 (這些對象可以像多線程那樣,通過參數傳遞給各個進程),用以同步進程,其用法與threading包中的同名類一致。
所以,multiprocessing的很大一部份與threading使用同一套API,只不過換到了多進程的情境。

但在使用這些共享API的時候,我們要注意以下幾點:

  • 在UNIX平臺上,當某個進程終結之後,該進程需要被其父進程調用wait,否則進程成爲殭屍進程(Zombie)。所以,有必要對每個Process對象調用join()方法 (實際上等同於wait)。對於多線程來說,由於只有一個進程,所以不存在此必要性。
  • multiprocessing提供了threading包中沒有的IPC(比如Pipe和Queue),效率上更高。應優先考慮Pipe和Queue,避免使用Lock/Event/Semaphore/Condition等同步方式 (因爲它們佔據的不是用戶進程的資源)。
  • 多進程應該避免共享資源。在多線程中,我們可以比較容易地共享資源,比如使用全局變量或者傳遞參數。在多進程情況下,由於每個進程有自己獨立的內存空間,以上方法並不合適。此時我們可以通過共享內存和Manager的方法來共享資源。但這樣做提高了程序的複雜度,並因爲同步的需要而降低了程序的效率。

Process.PID中保存有PID,如果進程還沒有start(),則PID爲None

我們可以從下面的程序中看到Thread對象和Process對象在使用上的相似性與結果上的不同。各個線程和進
程都做一件事:打印PID。但問題是,所有的任務在打印的時候都會向同一個標準輸出(stdout)輸出。這樣輸出的字符會混合在一起,無法閱讀。使用Lock同步,在一個任務輸出完成之後,再允許另一個任務輸出,可以避免多個任務同時向終端輸出

import os
import threading
import multiprocessing

# Main
print(‘Main:’, os.getpid())

# worker function
def worker(sign, lock):
lock.acquire()
print(sign, os.getpid())
lock.release()

# Multi-thread
record = []
lock = threading.Lock()

# Multi-process
record = []
lock = multiprocessing.Lock()

if name == main:
for i in range(5):
thread = threading.Thread(target=worker, args=(‘thread’, lock))
thread.start()
record.append(thread)

Main: 10012
thread 10012
thread 10012
thread 10012
thread 10012
thread 10012
Main: 6052
process 6052
Main: 8080
Main: 4284
Main: 7240
process 8080
process 4284
process 7240
Main: 10044
process 10044

Pipe和Queue

正如我們在Linux多線程中介紹的管道PIPE和消息隊列message queue,multiprocessing包中有Pipe類和Queue類來分別支持這兩種IPC機制。Pipe和Queue可以用來傳送常見的對象。

  1. Pipe可以是單向(half-duplex),也可以是雙向(duplex)。我們通過mutiprocessing.Pipe(duplex=False)創建單向管道 (默認爲雙向)。一個進程從PIPE一端輸入對象,然後被PIPE另一端的進程接收,單向管道只允許管道一端的進程輸入,而雙向管道則允許從兩端輸入。

下面的程序展示了Pipe的使用:

import multiprocessing as mul

def proc1(pipe):
pipe.send(‘hello’)
print(‘proc1 rec:’, pipe.recv())

def proc2(pipe):
print(‘proc2 rec:’, pipe.recv())
pipe.send(‘hello, too’)

# Build a pipe
pipe = mul.Pipe()
if name == main:
# Pass an end of the pipe to process 1
p1 = mul.Process(target=proc1, args=(pipe[0],))
# Pass the other end of the pipe to process 2
p2 = mul.Process(target=proc2, args=(pipe[1],))
p1.start()
p2.start()
p1.join()
p2.join()

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
proc2 rec: hello
proc1 rec: hello, too
  • 1
  • 2

這裏的Pipe是雙向的。

Pipe對象建立的時候,返回一個含有兩個元素的表,每個元素代表Pipe的一端(Connection對象)。我們對Pipe的某一端調用send()方法來傳送對象,在另一端使用recv()來接收。

  1. Queue與Pipe相類似,都是先進先出的結構。但Queue允許多個進程放入,多個進程從隊列取出對象。Queue使用mutiprocessing.Queue(maxsize)創建,maxsize表示隊列中可以存放對象的最大數量。

下面的程序展示了Queue的使用:

import os
import multiprocessing
import time
#==================
# input worker
def inputQ(queue):
    info = str(os.getpid()) + '(put):' + str(time.time())
    queue.put(info)

# output worker
def outputQ(queue,lock):
info = queue.get()
lock.acquire()
print (str(os.getpid()) + ’ get: ’ + info)
lock.release()
#===================
# Main
record1 = [] # store input processes
record2 = [] # store output processes
lock = multiprocessing.Lock() # To prevent messy print
queue = multiprocessing.Queue(3)

if name == main:
# input processes
for i in range(10):
process = multiprocessing.Process(target=inputQ,args=(queue,))
process.start()
record1.append(process)

<span class="token comment"># output processes</span>
<span class="token keyword">for</span> i <span class="token keyword">in</span> <span class="token builtin">range</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">:</span>
    process <span class="token operator">=</span> multiprocessing<span class="token punctuation">.</span>Process<span class="token punctuation">(</span>target<span class="token operator">=</span>outputQ<span class="token punctuation">,</span>args<span class="token operator">=</span><span class="token punctuation">(</span>queue<span class="token punctuation">,</span>lock<span class="token punctuation">)</span><span class="token punctuation">)</span>
    process<span class="token punctuation">.</span>start<span class="token punctuation">(</span><span class="token punctuation">)</span>
    record2<span class="token punctuation">.</span>append<span class="token punctuation">(</span>process<span class="token punctuation">)</span>

<span class="token keyword">for</span> p <span class="token keyword">in</span> record1<span class="token punctuation">:</span>
    p<span class="token punctuation">.</span>join<span class="token punctuation">(</span><span class="token punctuation">)</span>

queue<span class="token punctuation">.</span>close<span class="token punctuation">(</span><span class="token punctuation">)</span>  <span class="token comment"># No more object will come, close the queue</span>

<span class="token keyword">for</span> p <span class="token keyword">in</span> record2<span class="token punctuation">:</span>
    p<span class="token punctuation">.</span>join<span class="token punctuation">(</span><span class="token punctuation">)</span>
  • 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
  • 38
  • 39
  • 40
  • 41
  • 42
8572 get: 6300(put):1555486924.3676226
8136 get: 3464(put):1555486924.412625
9576 get: 9660(put):1555486924.5126307
6936 get: 5064(put):1555486924.5976355
10652 get: 8688(put):1555486924.5976355
6992 get: 10988(put):1555486924.7526445
6548 get: 6836(put):1555486924.7456443
3504 get: 7284(put):1555486924.7666454
8652 get: 4960(put):1555486924.8536503
10868 get: 460(put):1555486924.8606508
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

一些進程使用put()在Queue中放入字符串,這個字符串中包含PID和時間。另一些進程從Queue中取出,並打印自己的PID以及get()的字符串。

進程池

進程池 (Process Pool)可以創建多個進程。這些進程就像是隨時待命的士兵,準備執行任務(程序)。一個進程池中可以容納多個待命的進程。

import multiprocessing as mul

def f(x):
return x ** 2

if name == main:
pool = mul.Pool(5)
rel = pool.map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
print(rel)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
  • 1

我們創建了一個容許5個進程的進程池 (Process Pool) 。Pool運行的每個進程都執行f()函數。我們利用map()方法,將f()函數作用到表的每個元素上。這與built-in的map()函數類似,只是這裏用5個進程並行處理。如果進程運行結束後,還有需要處理的元素,那麼的進程會被用於重新運行f()函數。除了map()方法外,Pool還有下面的常用方法。

apply_async(func,args) 從進程池中取出一個進程執行func,args爲func的參數。它將返回一個AsyncResult的對象,你可以對該對象調用get()方法以獲得結果。

close() 進程池不再創建新的進程

join() wait進程池中的全部進程。必須對Pool先調用close()方法才能join。

共享內存

實例代碼:

import multiprocessing

# Value/Array
def func1(a, arr):
a.value = 3.14
for i in range(len(arr)):
arr[i] = 0
a.value = 0

if name == main:
num = multiprocessing.Value(‘d’, 1.0) # num=0
arr = multiprocessing.Array(‘i’, range(10)) # arr=range(10)
p = multiprocessing.Process(target=func1, args=(num, arr))
p.start()
p.join()
print (num.value)
print (arr[:])

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
0.0
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
  • 1
  • 2

這裏我們實際上只有主進程和Process對象代表的進程。我們在主進程的內存空間中創建共享的內存,也就是Value和Array兩個對象。對象Value被設置成爲雙精度數(d), 並初始化爲1.0。而Array則類似於C中的數組,有固定的類型(i, 也就是整數)。在Process進程中,我們修改了Value和Array對象。回到主程序,打印出結果,主程序也看到了兩個對象的改變,說明資源確實在兩個進程之間共享。

Manager

Manager是通過共享進程的方式共享數據。
Manager管理的共享數據類型有:Value、Array、dict、list、Lock、Semaphore等等,同時Manager還可以共享類的實例對象。
實例代碼:

from multiprocessing import Process,Manager
def func1(shareList,shareValue,shareDict,lock):
    with lock:
        shareValue.value+=1
        shareDict[1]='1'
        shareDict[2]='2'
        for i in xrange(len(shareList)):
            shareList[i]+=1

if name == main:
manager=Manager()
list1=manager.list([1,2,3,4,5])
dict1=manager.dict()
array1=manager.Array(‘i’,range(10))
value1=manager.Value(‘i’,1)
lock=manager.Lock()
proc=[Process(target=func1,args=(list1,value1,dict1,lock)) for i in xrange(20)]
for p in proc:
p.start()
for p in proc:
p.join()
print list1
print dict1
print array1
print value1

    [21, 22, 23, 24, 25]
    {1: '1', 2: '2'}
    array('i', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
    Value('i', 21)
    
    發表評論
    所有評論
    還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
    相關文章