python3進程multiprocessing模塊筆記(一)

一、multiprocessing簡介

官方文檔

1、multiprocessing概述

multiprocessing 是一個用於創建進程的包,具有與 threading 模塊相似API。 multiprocessing 包同時提供本地和遠程併發,使用子進程代替線程,有效避免 Global Interpreter Lock 帶來的影響。因此, multiprocessing 模塊允許程序員充分利用機器上的多核。

2、Process 概述

Process 是 multiprocessing 中的類,通過創建一個 Process 對象然後調用它的 start() 方法來創建子進程。 Process 和 threading.Thread API 大致相同:

class multiprocessing.Process(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)  # 使用關鍵字參數調用構造函數

簡單舉例:

import multiprocessing
import os

def info(title):
    print(title)
    print('parent process:', os.getppid())  # 父進程ID
    print('process id:', os.getpid())  # 當前進程ID


if __name__ == '__main__':
    print('process id:', os.getpid())
    print('----------------------')
    info('main process')
    p = multiprocessing.Process(target=info, args=('subprocess',))
    p.start()

二、進程間的隊列通信Queue

進程之間通信可以使用Queue方法,通常自定義函數中引用全局變量:global 變量名不能起到進程之間的通信作用。

import multiprocessing
import os
import time

list_1 = [2]
def write_process(queue):
    global list_1  # 使用全局變量,這裏global是讀取複製了一個list_1,修改list_1,不會對外面的全局變量list_1起任何作用
    i = 0
    while True:
        if queue.full():  # 判斷消息隊列是否已滿
            print('已經寫滿')
            print('進程名稱:', multiprocessing.current_process())  # 獲取當前進程的名稱
            break
        else:
            print(f'寫入數據{i}')
            # put()方法向消息隊列中增加數據,有兩個可選參數, block和 timeout ,
            # block默認是Ture,消息列隊滿後程序阻塞,等待有數據取出後再寫入;如果修改爲False消息列隊滿後會拋出queue.Full異常
            # timeout設置等待時間,在block=False的情況下,等待時間後仍然無法寫入數據才拋出異常,否則立即拋出異常
            queue.put(i)
            list_1.append(i)
            i += 1
        time.sleep(0.5)
    print(list_1)

def read_process(queue):
    time.sleep(0.5)
    while True:
        if queue.empty():  # 判斷消息隊列是否爲空
            print('列隊爲空')
            print('進程名稱:', multiprocessing.current_process())
            break
        else:
            # get()從消息隊列中依此取值並將其從隊列中移除,不能任意取值,get()有兩個可選參數 block 和 timtout,
            # block默認True,當隊列中數據被全部數據爲空時會阻塞程序,等待新數據寫入。如果修改爲False則會立即拋出異常_queue.Empty
            # timtout 同理put的參數
            value = queue.get()  # 取出消息隊列的一個數據
            print('讀取數據:', value)

def multi_queue():
    # 創建消息隊列,可以增加一個整數型參數,設置消息隊列的數據數量,如果不設參數或者參數爲負值,則默認爲沒有上限,直到內存用盡
    queue = multiprocessing.Queue(5)
    w_process = multiprocessing.Process(target=write_process, args=(queue,), name='wuwei_w')
    r_process = multiprocessing.Process(target=read_process, args=(queue,), name='wuwei_r')
    w_process.start()
    w_process.join()  # 設置運行先後順序
    r_process.start()
    r_process.join()  # 這裏可以用來阻塞主進程,等子進程結束在運行主進程
    print(queue.qsize())  # 獲取消息隊列中數據數量
    print(list_1)  # 這裏注意雖然在子進程中修改了list_1,但是在全局變量list_1的值始終未改變


if __name__ == '__main__':
    print(multiprocessing.cpu_count())  # 主機CPU核心數
    # 指定進程啓動的方法,['fork','spawn','forkserver'] spawn可用於windows(默認)、macOS(默認)和unix,fork僅用於unix(默認)
    multiprocessing.set_start_method('spawn', True)  # 開發環境和運行環境相同時可以不指定,否則最好指定
    multi_queue()

三、鎖(Lock)來實現進程間數據修改同步

類似於threading 的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()

四、進程間共享變量

1、共享內存( Value 和 Array )

Value 或 Array 可以將數據存儲在共享內存映射中


def func(num, array):
    num.value = 1.11
    for i in range(len(array)):
        array[i] = -array[i]

def multi_value_and_array():
    # Value(typecode_or_type, *args) 第一個參數是指定數據類型(注意沒有字符串格式,僅float和int),第二個參數可以接受變量的值
    # typecode_or_type的類型使用簡寫並且帶'',包括:'f'和'd'等同於: float;'b'/'B'/'h'/'H'/'i'/'I'/'l'/'L'等同於:int
    num = multiprocessing.Value('d', 0.0)   # 創建普通共享內存變量
    arr = multiprocessing.Array('i', range(10))

    p = multiprocessing.Process(target=func, args=(num, arr))
    p.start()
    p.join()

    print(num.value)
    print(arr[:])

輸出結果:
1.11
[0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
multiprocessing.Value(typecode_or_type, *args)的參數類型: typecode可爲以下Type Code列類型:

Type Code Python Type
‘c’ character
‘b’ int
‘B’ int
‘u’ character
‘h’ int
‘H’ int
‘i’ int
‘I’ int
‘l’ int
‘L’ int
‘f’ float
‘d’ float

2、共享服務進程Manager

由 Manager() 返回的管理器對象控制一個服務進程,該進程保存Python對象並允許其他進程使用代理操作它們。
Manager() 返回的管理器支持類型: list 、 dict 、 Namespace 、 Lock 、 RLock 、 Semaphore 、 BoundedSemaphore 、 Condition 、 Event 、 Barrier 、 Queue 、 Value 和 Array 。
可以看到Manager() 也可以管理Value 和 Array,參數定義基本相同,只是取值賦值需要通過get()/set()方法進行。
例如:

# 不能將共享變量定義成全局變量然後通過global引用那樣會報錯,只能通過參數傳遞。但如果是在同一個類中,可以用self定義共享鎖和變量
def sub_process(process_name, share_var, share_value, share_lock):
    share_lock.acquire()  # 獲取鎖
    share_var.append(process_name)  # 這裏可以直接使用list的大部分方法,但是clear()方法會報錯
    print(share_value.get())  # 獲取共享變量share_value
    share_value.set(share_value.get()+2.0)
    share_lock.release()  # 釋放鎖
    for item in share_var:
        print(f"{process_name}-{item}")

def main_process():
    # 單個值聲明方式。typecode是進制類型,C寫法和Python寫法都可以;value是初始值。
    # 這種單值形式取值賦值需要通過get()/set()方法進行,不能直接如一般變量那樣取值賦值
    share_value = multiprocessing.Manager().Value('f', 0.0)
    # 數組聲明方式。typecode是數組變量中的變量類型,sequence是數組初始值
    # share_arr = multiprocessing.Manager().Array(typecode, sequence)
    # 字典聲明方式
    # share_dict = multiprocessing.Manager().dict()
    # 列表聲明方式
    share_list = multiprocessing.Manager().list()
    share_list.append("start flag")
    # 聲明一個共享鎖
    share_lock = multiprocessing.Manager().Lock()
    process_list = []

    process_name = "process 1"
    tmp_process = multiprocessing.Process(target=sub_process, args=(process_name, share_list, share_value, share_lock))
    process_list.append(tmp_process)

    process_name = "process 2"
    tmp_process = multiprocessing.Process(target=sub_process, args=(process_name, share_list, share_value, share_lock))
    process_list.append(tmp_process)

    for process in process_list:
        process.start()
    for process in process_list:
        process.join()


if __name__ == '__main__':
    main_process()

輸出如下:
0.0
process 1-start flag
process 1-process 1
2.0
process 2-start flag
process 2-process 1
process 2-process 2

class Multi_share(object):
    def __init__(self):
        # 單個值聲明方式。typecode是進制類型,C寫法和Python寫法都可以;value是初始值。
        # 這種單值形式取值賦值需要通過get()/set()方法進行,不能直接如一般變量那樣取值賦值
        self.share_value = multiprocessing.Manager().Value('f', 0.0)
        # 數組聲明方式。typecode是數組變量中的變量類型,sequence是數組初始值
        # share_arr = multiprocessing.Manager().Array(typecode, sequence)
        # 字典聲明方式
        # share_dict = multiprocessing.Manager().dict()
        # 列表聲明方式
        self.share_list = multiprocessing.Manager().list()
        self.share_list.append("start flag")
        # 聲明一個共享鎖
        self.share_lock = multiprocessing.Manager().Lock()

        self.process_name = "process 1"
        self.process_name = "process 2"
        # 同一個類中,可以用self定義共享鎖和變量

    def sub_process(self):
        self.share_lock.acquire()  # 獲取鎖
        self.share_list.append(self.process_name)  # 這裏可以直接使用list的大部分方法,但是clear()方法會報錯
        print(self.share_value.get())  # 獲取共享變量share_value
        self.share_value.set(self.share_value.get() + 2.0)
        self.share_lock.release()  # 釋放鎖
        for item in self.share_list:
            print(f"{self.process_name}-{item}")

    def main_processing(self):
        process_list = []
        tmp_process = multiprocessing.Process(target=self.sub_process)
        process_list.append(tmp_process)

        tmp_process = multiprocessing.Process(target=self.sub_process)
        process_list.append(tmp_process)

        for process in process_list:
            process.start()
            process.join()


if __name__ == '__main__':
    a = Multi_share()
    a.main_processing()

把進程鎖和共享變量在類中初始化中使用self定義是完全可以使用大的,輸出的結果與上面是完全相同的:
0.0
process 2-start flag
process 2-process 2
2.0
process 2-start flag
process 2-process 2
process 2-process 2

3、共享實例化對象global和BaseManager

通過global共享的實例化對象是隻讀的,不可以對實例化對象做任何修改,其實修改也不會報錯,只是輸出結果讓你感到迷茫。直接使用BaseManager可以解決這些問題,但是使用BaseManager有個很奇怪的地方,必須使用“from multiprocessing.managers import BaseManager”導入,如果只導入import multiprocessing,然後multiprocessing.managers.BaseManager()就會報錯:AttributeError: module ‘multiprocessing’ has no attribute ‘managers’,怪哉!怪哉!(不知道是不是pycharm的問題)

# 定義一個要共享實例化對象的類
class TestMulti(object):
    def __init__(self):
        self.test_list = ["start flag"]

    def add_list(self, arg):
        self.test_list.append(arg)
        print('-------')

    def print_list(self):
        print(self.test_list)


share_lock = multiprocessing.Lock()  # 鎖可以通過global傳遞,也可以在Process中傳參
text = TestMulti()  # 實例化對象也可以通過global傳遞進去,並且可以執行類方法,但是對類內變量做任何修改都是無效的


def test_sub_process(process_name, obj):
    global share_lock
    global text
    share_lock.acquire()
    obj.add_list(f"{process_name}")
    share_lock.release()
    # 在這裏使用text調用add_list方法不會報錯,而且add_list方法的內print('-------')也執行了,但是"wuwei"卻沒有添加到列表中
    text.add_list("wuwei")
    obj.print_list()

def test_main_process():
    from multiprocessing.managers import BaseManager
    # 爲了更加直接我們直接以一個Test類的實例化對象來演示   multiprocessing.managers
    manager = BaseManager()
    # 必須要在start前註冊,不然就註冊無效
    manager.register('Test', TestMulti)  # 第一個參數是要註冊實例對象名稱(後面使用這個名稱),第二個參數是共享實例對象
    manager.start()
    obj = manager.Test()
    # 註冊系統函數open可以如下操作
    # manager = BaseManager()
    # # 一定要在start前註冊,不然就註冊無效
    # manager.register('open', open)
    # manager.start()
    # obj = manager.open("1.txt","a")

    process_list = []
    # 創建進程1
    process_name = "process 1"
    tmp_process = multiprocessing.Process(target=test_sub_process, args=(process_name, obj))
    process_list.append(tmp_process)
    # 創建進程2
    process_name = "process 2"
    tmp_process = multiprocessing.Process(target=test_sub_process, args=(process_name, obj))
    process_list.append(tmp_process)
    # 啓動所有進程
    for process in process_list:
        process.start()
        process.join()


if __name__ == '__main__':
    print(multiprocessing.cpu_count())  # 主機CPU核心數
    # 指定進程啓動的方法,['fork','spawn','forkserver'] spawn可用於windows(默認)、macOS(默認)和unix,fork僅用於unix(默認)
    multiprocessing.set_start_method('spawn', True)  # 開發環境和運行環境相同時可以不指定,否則最好指定
    # multi()  # 使用進程
    # multi_queue()  # 進程間傳遞消息queue和global
    # main_process()  # 進程間共享變量
    # multi_value_and_array()
    # a = Multi_share()  # 進程間以類的方式共享變量
    # a.main_processing()
    test_main_process()
    text.print_list()

輸出結果如下:

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