說到多進程首先想到的就是多進程間的通信方式:
- 管道(PIPE)
- 信號(Signal)
- 消息隊列(Message Queue)
- 共享內存(Shared Memory)
- 信號量(Semaphore)
- 套接字(Socket)
然後就是C++多進程的實現:fork()函數,fork()很特殊有兩個返回值,一個是子進程,另一個0或者子進程ID,python的os包中也帶有fork函數也是類似的用法,但是問題就在與fork分叉的方法不能用於windows並且使用起來並不順手,所以封裝了多進程各種操作的包multiprocessing就變的非常重要。
multiprocessing官方文檔:
https://docs.python.org/3/library/multiprocessing.html
相較於C/C++,Python的多線程因爲歷史原因,GIL鎖問題會讓Python多線程(threading)不能有效的利用多核心CPU,而C/C++線程對多核的利用效率可以達到100%,所以在Python中想提高線程對多核CPU的利用率要用C/C++來擴展,還好Python對於C擴展還是很容易,但是也有更好的解決方案那就是多進程實現併發。
1.Process
官方例子:
from multiprocessing import Process
def f(name):
print('hello', name)
if __name__ == '__main__':
p = Process(target=f, args=('bob',))
p.start()
p.join()
其中join是爲進程添加阻塞,能保證p進程執行完成後,主進程纔會退出。
更復雜一點:
from multiprocessing import Process
import os
def info(title):
print(title)
print('module name:', __name__)
print('parent process:', os.getppid())
print('process id:', os.getpid())
def f(name):
info('function f')
print('hello', name)
if __name__ == '__main__':
info('main line')
p = Process(target=f, args=('bob',))
p.start()
p.join()
這段代碼不但會打印出hello bob還會打印出子進程的pid和父進程的pid
2.進程池Pool
看作是進程對象的容器,可以對進程進行分配和管理
利用Pool創建多個進程apply和apply_async
apply(func [,args [,kwds ] ] )回調func帶參數args和關鍵字kwds,會阻塞,所以一般使用
apply_async(func [,args [,kwds [,callback [,error_callback ] ] ] ] )回調func函數帶參數args和關鍵字kwds,非阻塞適合並行操作。
from multiprocessing import Pool
import os, time, random
def long_time_task(name):
print('Run task %s (%s)...' % (name, os.getpid()))
start = time.time()
time.sleep(random.random() * 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(4)
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.')
官方給出的例子:
from multiprocessing import Pool
import time
def f(x):
return x*x
if __name__ == '__main__':
with Pool(processes=4) as pool: # start 4 worker processes
result = pool.apply_async(f, (10,)) # evaluate "f(10)" asynchronously in a single process
print(result.get(timeout=1)) # prints "100" unless your computer is *very* slow
print(pool.map(f, range(10))) # prints "[0, 1, 4,..., 81]"
it = pool.imap(f, range(10))
print(next(it)) # prints "0"
print(next(it)) # prints "1"
print(it.next(timeout=1)) # prints "4" unless your computer is *very* slow
result = pool.apply_async(time.sleep, (10,))
print(result.get(timeout=1)) # raises multiprocessing.TimeoutError
其中的map函數就是內建函數的一種實現方式,是阻塞的(不阻塞的是map_async),但是在pool.map中不能使用匿名函數。imap返回的是一個迭代器。
3.進程間通信
隊列Queue
from multiprocessing import Process, Queue
def f(q):
q.put([42, None, 'hello'])
if __name__ == '__main__':
q = Queue()
p = Process(target=f, args=(q,))
p.start()
print(q.get()) # prints "[42, None, 'hello']"
p.join()
簡單的消息隊列
from multiprocessing import Queue
from multiprocessing import Process
import random
import time
import os
def foo(name,task):
if not task.empty():
temp=task.get()
print('%s pid:%s get %s parentsid:%d'%(name,os.getpid(),temp,os.getppid()))
else:
print('%s find the Queue is empty'%name)
if __name__=='__main__':
resource=['a','b','c','d','e','f','g','h','i','j','k','l','m','n']
x=random.sample(resource,4)
task = Queue()
for i in x:
task.put(i)
for i in range(5):
Process(target=foo,args=('process%d'%(i+1),task)).start()
time.sleep(1)
運行結果
process2 pid:8964 get f parentsid:9128
process3 pid:7560 get h parentsid:9128
process4 pid:8340 get d parentsid:9128
process1 pid:7564 get a parentsid:9128
process5 find the Queue is empty
管道(PIPE)
官方例子
from multiprocessing import Process, Pipe
def f(conn):
conn.send([42, None, 'hello'])
conn.close()
if __name__ == '__main__':
parent_conn, child_conn = Pipe()
p = Process(target=f, args=(child_conn,))
p.start()
print(parent_conn.recv()) # prints "[42, None, 'hello']"
p.join()
pipe()創建的是一個雙工的管道連接對象。這個例子中將 child_conn 傳遞給子進程,子進程發送信息,主進程用parent_conn連接對象接受子進程發送的消息。
請注意:如果兩個進程(或線程)試圖同時讀取或寫入管道的同一端,則管道中的數據可能會損壞。當然,同時使用不同管道的流程也不會有數據損壞風險。
鎖機制(LOCK)
與線程中的鎖機制差不多,利用鎖機制可以使多個進程同步,解決對資源的爭搶衝突。
官方例子:
from multiprocessing import Process, Lock
def f(l, i):
l.acquire()
try:
print('hello world', i)
finally:
l.release()
if __name__ == '__main__':
lock = Lock()
for num in range(10):
Process(target=f, args=(lock, num)).start()
同步常見防止管道衝突:
from multiprocessing import Process
from multiprocessing import Pipe
from multiprocessing import Lock
def foo(name,conn,lock):
lock.acquire()
try:
conn.send('%s send hello'%name)
finally:#就算執行不成功也釋放 否則進程會被鎖死
lock.release()
if __name__=='__main__':
pconn,conn=Pipe()
kt=Lock()
for i in range(5):
Process(target=foo,args=('process%d'%(i+1),conn,kt)).start()
for i in range(5):
print(pconn.recv())
print('process over!')
共享內存(Shared memory)
使用Arry和Value,官方的例子如下:
from multiprocessing import Process, Value, Array
def f(n, a):
n.value = 3.1415927
for i in range(len(a)):
a[i] = -a[i]
if __name__ == '__main__':
num = Value('d', 0.0)
arr = Array('i', range(10))
p = Process(target=f, args=(num, arr))
p.start()
p.join()
print(num.value)
print(arr[:])
輸出結果:
3.1415927
[0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
Value(name,value)將value綁定到name Array也是這樣。
多進程還有一個好處就是因爲,服務器和客戶端之間,服務器與服務器之間,客戶端與客戶端的通信也是進程間通信,通過Manger可以實現分佈式的進程,將任務分配給多個服務器進行處理,在廖雪峯的教程中也講到了BaseManger,這個很有用,會在後面進行總結。