目錄
一、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']