Python由於GIL的存在,多線程(Thread)、協程(Asyncio)可以實現併發,並行則依賴多進程(Multiprocessing)實現。
多進程的學習可以參考廖雪峯Python教程和Python標準庫。
https://www.liaoxuefeng.com/wiki/1016959663602400/1017628290184064
https://docs.python.org/zh-cn/3.7/library/multiprocessing.html
本文就Multiprocessing的日常使用做個demo。
一、通過Process創建多進程
實現多進程,可以創建多個Process對象,並調用start()去生成進程,通過join()等待完成。
from multiprocessing import Process
import os
import time
import random
def run(name: str):
print(f"Child process {os.getpid()} do {name} task, parent process {os.getppid()}")
start = time.time()
time.sleep(random.random() * 3)
end = time.time()
print(f'Task {name} runs {end - start} seconds.')
def main():
# test process
start = time.time()
print(f'Parent process {os.getpid()}')
p1 = Process(target=run, args=('test1',))
p2 = Process(target=run, args=('test2',))
p1.start()
p2.start()
p1.join()
p2.join()
print('Child process end')
end = time.time()
print(f'All Tasks runs {end - start} seconds.')
if __name__ == '__main__':
main()
效果如圖,parent進程62488,創建的子進程分別爲62489和62450,共用時0.38s,小於分別執行的0.3s和0.36s之和,說明並行。
二、通過Pool進程池創建
一般情況下,不會通過Process這種低級API去創建多進程,可以通過Pool進程池去創建。Pool(num),num指定工作進程數目,默認通過os.cpu_count()獲取CPU核數,通過apply_async註冊函數。
from multiprocessing import Pool
import subprocess
import os
import time
import random
def pool_run(name: str):
print(f'Child process {os.getpid()} Run task {name}')
start = time.time()
time.sleep(random.random() * 3)
end = time.time()
print(f'Task {name} runs {end - start} seconds.')
return name
def main():
# test pool
print(f'Parent process {os.getpid()}')
p = Pool(4)
result = []
for i in range(5):
result.append(p.apply_async(pool_run, args=(i,)))
p.close()
p.join()
for res in result:
print(res.get())
print('All subprocesses done.')
if __name__ == '__main__':
main()
效果如圖,總共5個任務,4個進程。parent進程62577,同時創建4個子進程去處理任務,在完成其中1個任務後,該空餘的進程繼續執行第5個任務。
三、進程間通信
Multiprocessing提供兩種標準的進程通信方式Quere、Pipe。在比如生產者-消費者這種模型可能會用到。
from multiprocessing import Process, Queue
import os
import time
import random
def write(q):
print(f'Process to write: {os.getpid()}')
for value in ['A', 'B', 'C']:
print(f'Put {value} to queue...')
q.put(value)
time.sleep(random.random())
def read(q):
print(f'Process to read: {os.getpid()}')
while True:
value = q.get(True)
print(f'Get {value} from queue.')
def main():
# test queue
q = Queue()
pw = Process(target=write, args=(q,))
pr = Process(target=read, args=(q,))
# 啓動子進程pw,寫入:
pw.start()
# 啓動子進程pr,讀取:
pr.start()
# 等待pw結束:
pw.join()
# pr進程裏是死循環,無法等待其結束,只能強行終止:
pr.terminate()
if __name__ == '__main__':
main()
效果如圖,進程62773生產,進程62774消費,兩者並行,通過隊列queue做通信。
四、總結
可能一般情況下,用Pool即可。