多進程multiprocessing間通訊和數據共享

1、進程簡介

io密集型的儘量使用多線程,如socketserver.

multiprocessing模塊可以使用一些簡單的API批量生生進程,可以實現本地和遠程進程間同步,通過使用子進程而不是線程來有效避開了全局解釋器鎖GIL,因此,該multiprocessing模塊允許程序員在給定機器上充分利用多個處理器。它可以在Unix和Windows上運行。

一個簡單的例子如下:

from multiprocessing import Process

def f(name):
    print 'hello', name

if __name__ == '__main__':
    p = Process(target=f, args=('bob',))
    p.start()
    p.join()

爲了顯示所涉及的各個進程ID,下面是一個擴展的示例:

# -*- coding: utf-8 -*-
#@Author: kangdan
#@time   :2019/12/12 10:42
#@Software:PyCharm Community Edition


from multiprocessing import Process
import os

def info(title):
    print(title)
    print('module name:', __name__)
    if hasattr(os, 'getppid'):  # only available on Unix
        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()

輸出:

2、進程間通訊和數據共享

不同進程間內存是不共享的,要想實現兩個進程間的數據交換,可以用以下方法:

2.1、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()

不可以在主程序中通過將q設置爲global方式在子進程中使用,因爲不同進程間數據是不共享的!!!

2.2、Pipe管道(不常用,一般使用Queue)

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()代表管道的兩端。每個連接對象都有send()和 recv()方法(以及其他方法)。請注意,如果兩個進程(或線程)試圖同時從管道的同一端讀取或寫入管道的同一端,則管道中的數據可能會損壞。當然,不存在同時使用管道不同端的過程造成損壞的風險。

2.3、Managers

由返回的管理器對象Manager()控制着一個服務器進程,該進程保存Python對象,並允許其他進程使用代理對其進行操作。

通過返回的經理Manager()將支持類型 listdictNamespaceLock, RLockSemaphoreBoundedSemaphore, ConditionEventBarrier, QueueValueArray。例如,

from multiprocessing import Process, Manager

def f(d, l):
    d[1] = '1'
    d['2'] = 2
    d[0.25] = None
    l.reverse()

if __name__ == '__main__':
    with Manager() as manager:
        d = manager.dict()
        l = manager.list(range(10))

        p = Process(target=f, args=(d, l))
        p.start()
        p.join()

        print(d)
        print(l)

將打印

{0.25: None, 1: '1', '2': 2}
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

服務器進程管理器比使用共享內存對象更靈活,因爲可以使它們支持任意對象類型。同樣,單個管理器可以由網絡上不同計算機上的進程共享。但是,它們比使用共享內存慢。

2.4、Lock

非遞歸鎖對象:模擬threading.Lock。一旦進程或線程獲取了鎖,則隨後從任何進程或線程獲取鎖的嘗試都將阻塞,直到釋放爲止;否則,該鎖將被釋放。任何進程或線程都可能釋放它。

請注意,Lock實際上這是一個工廠函數,該函數返回multiprocessing.synchronize.Lock使用默認上下文初始化的實例。

Lock支持上下文管理器協議,因此可以在with語句中使用。

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()

2.5、Pool進程池

進程池內部維護一個進程序列,當使用時,則去進程池中獲取一個進程,如果進程池序列中沒有進程,那麼程序就會等待,直到進程池中有可用進程爲止。

進程池有兩種方法:

  • apply
  • apply_async
from  multiprocessing import Pool,freeze_support
import time

def Foo(i):
    time.sleep(2)
    return i + 100

def Bar(arg):
    print('-->exec done:', arg)

if __name__ == '__main__':
    freeze_support()
    pool = Pool(5)

    for i in range(10):
        pool.apply_async(func=Foo, args=(i,), callback=Bar)#異步
        #callback回調函數,執行完Foo,會自動執行Bar,並且把Foo的返回值傳給Bar參數
        # pool.apply(func=Foo, args=(i,))#同步,沒有callback

    print('end')
    pool.close()
    pool.join()  # 進程池中進程執行完畢後再關閉,如果註釋,那麼程序直接關閉。

 

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