Python多進程multiprocessing

說到多進程首先想到的就是多進程間的通信方式:

  1. 管道(PIPE)
  2. 信號(Signal)
  3. 消息隊列(Message Queue)
  4. 共享內存(Shared Memory)
  5. 信號量(Semaphore)
  6. 套接字(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,這個很有用,會在後面進行總結。

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