簡介
multiprocessing
是一個使用類似於threading
模塊的API支持生成進程的包。該multiprocessing
軟件包提供本地和遠程並發。因此,該multiprocessing
模塊允許程序員充分利用給定機器上的多個處理器。可以在Unix和Windows上運行。
Process
(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)
應該始終使用關鍵字參數調用構造函數
- group參數永遠為None,該參數僅用於兼容
threading.Thread
- target應該是一個可以調用的對象,它會被run()方法調用
- name是進程名
- args是target所調用方法的參數
- kwargs是target所調用方法的關鍵字參數
- daemon默認為None,意味着從創建進程中繼承,可設為True(守護進程)或False(非守護進程)
start()
- 啟動進程,只能調用一次,他會在進程中調用run方法
join
([timeout])
- 設主進程為m,子進程為s,m中調用s.join():阻塞m,直到s進程結束,timeout是一個正數,它最多會阻塞timeout秒 ,另外,s.join()可調用若干次
- 一個進程p調用自己進程的join (p.join()) 可能會導致死鎖【自己join自己這種騷操作不會,因此沒實驗】
- 只能在調用s.start()后調用s.join()
join可防止產生僵屍進程,文檔中的編程指南中指出: 每次開啟一個新進程,所有未被join的進程會被join(也就是說非守護進程會自動被join),但即便如此也要明確地join啟動的所有進程。 因此如果不手動地join子線程,主進程也會等待子進程中止之后再中止
【另外join會影響守護進程的行為,后面探討】
(一)Process開啟進程:
# -*- coding:utf-8 -*- import os from multiprocessing import Process def func(name): print('%s進程%d,父進程%d' % (name, os.getpid(), os.getppid())) ''' 在Windows中,Process開啟進程會再次導入此文件,為防止導入時再次執行,需要添加 if __name__ == '__main__': ''' if __name__ == '__main__': func('主') p = Process(target=func, args=['子']) p.start() p.join()
結果:
主進程16452,父進程21852
子進程28472,父進程16452
(二)自定義類繼承Process,重寫run方法
如果子類重寫構造函數,則必須確保它在對進程執行任何其他操作之前調用構造函數 [Process.__init__() ]。
# -*- coding:utf-8 -*- from multiprocessing import Process import time import os class InheritTest(Process): def __init__(self, name): super().__init__() self.name = name def run(self): print('我是子進程%s,進程id=%d,父進程id=%d' % (self.name, os.getpid(), os.getppid())) time.sleep(2) if __name__ == '__main__': print('我是主進程, 進程id=%d,父進程id=%d' % (os.getpid(), os.getppid())) p = InheritTest('小明') p.start() p.join()
結果:
我是主進程, 進程id=18408,父進程id=21852
我是子進程小明,進程id=22640,父進程id=18408
若多個進程被join,阻塞時長是他們(包括主進程在這期間的執行時間)中執行時間最長的
# -*- coding:utf-8 -*- from multiprocessing import Process import time def short_time(): print('進程pid={},ppid={}'.format(os.getpid(), os.getppid())) time.sleep(2) def long_time(): print('進程pid={},ppid={}'.format(os.getpid(), os.getppid())) time.sleep(4) if __name__ == "__main__": p1 = Process(target=long_time) p2 = Process(target=short_time) p3 = Process(target=short_time) p4 = Process(target=short_time) p1.start() p2.start() p3.start() p4.start() print('1號進程阻塞中') p1.join() print('2號進程阻塞中') p2.join() print('3號進程阻塞中') p3.join() print('4號進程阻塞中') p4.join() ''' p1-p4異步執行,p1執行時間最長,那么p1.join()阻塞完后,其它進程已經執行完了, p2-p4.join()不阻塞,直接執行 '''
結果:
1號進程阻塞中
進程pid=26404,ppid=17928
進程pid=17960,ppid=17928
進程pid=15592,ppid=17928
進程pid=8724,ppid=17928
2號進程阻塞中
3號進程阻塞中
4號進程阻塞中
問1:只能在父進程中join子進程嗎?
問1的解釋: 在子進程中join其它同級的子進程會拋出異常 AssertionError: can only join a child process,因此只能在父進程中join子進程:
# -*- coding:utf-8 -*- from multiprocessing import Process import time import os def func1(): time.sleep(2) print('func1 進程pid={} ppid={}'.format(os.getpid(), os.getppid())) def func2(p1): p1.join() print('func2 進程pid={} ppid={}'.format(os.getpid(), os.getppid())) if __name__ == "__main__": print('主進程id {}'.format(os.getpid())) p1 = Process(target=func1) p2 = Process(target=func2, args=[p1]) p2.start() # 和下面代碼互換會報另一個異常 p1.start()
主進程id 14796
Process Process-2:
Traceback (most recent call last):
...............
AssertionError: can only join a child process
func1 進程pid=30100 ppid=14796
run()
- 表示進程活動的方法。調用target指定的函數,如果有參數,會按順序傳入
- 自定義進程類要在子類中覆蓋此方法。
【單獨調用run不會開啟子進程】
is_alive()
- 查看進程是否還活着。
- 粗略地說,從
start()
方法返回到子進程終止的那一刻,進程對象處於活動狀態。
pid
- 返回進程ID。在產生該過程之前,這將是
None
。
exitcode
- 子進程的退出碼,如果進程尚未中止,返回None,如果是-N,表示被信號N中止
# -*- coding:utf-8 -*- import os from multiprocessing import Process import time def func(name): print('%s進程%d,父進程%d' % (name, os.getpid(), os.getppid())) if name != '主': time.sleep(2) if __name__ == '__main__': func('主') p = Process(target=func, args=['子']) p.start() print('p.pid()', p.pid) print('p.is_alive()=', p.is_alive()) print('p.exitcode=', p.exitcode) p.join() print('p.exitcode=', p.exitcode) print('p.is_alive()=', p.is_alive())
結果
主進程1548,父進程21852
p.pid() 24000
p.is_alive()= True
p.exitcode= None
子進程24000,父進程1548
p.exitcode= 0
p.is_alive()= False
daemon
- 進程的守護進程標志,一個布爾值。必須在
start()
調用之前設置 它。 - 初始值繼承自創建過程。
- 當進程退出時(更確切地說是該進程的代碼執行完畢后),它會嘗試終止其所有守護子進程。
- 不允許守護進程創建子進程。否則,守護進程會在進程退出時使這些進程變成孤兒進程。此外,這些不是Unix守護程序或服務,它們是正常進程,如果非守護進程已退出,它們將被終止(不會被join)。
(三)開啟一個守護進程:
# -*- coding:utf-8 -*- from multiprocessing import Process import time import os def daemon_func(): time.sleep(2) print('守護進程pid={},ppid={}'.format(os.getpid(), os.getppid())) if __name__ == "__main__": print('主進程id {}'.format(os.getpid())) p = Process(target=daemon_func) p.daemon = True p.start() # p.join()
當p.join()注釋后,主進程中止,守護進程也中止,因此輸出:
主進程id 15096
若取消注釋,守護進程被join,主進程會等待此守護進程,輸出為:
主進程id 10896
守護進程pid=19404,ppid=10896
(四) 主進程分別開啟一個守護進程和非守護進程:
# -*- coding:utf-8 -*- from multiprocessing import Process import time import os # 守護進程p1執行 def daemon_func(): time.sleep(1) print('daemon_func 進程pid={},ppid={}'.format(os.getpid(), os.getppid())) # 非守護進程p2執行 def non_daemon_func(): time.sleep(2) print('non_daemon_func 進程pid={},ppid={}'.format(os.getpid(), os.getppid())) if __name__ == "__main__": print('主進程id {}'.format(os.getpid())) p1 = Process(target=daemon_func) p1.daemon = True p2 = Process(target=non_daemon_func) p1.start() p2.start()
主進程執行完立即中止守護進程(主進程此時自己沒中止),主進程等待非守護進程,然后中止
因此執行結果是:
主進程id 24588
non_daemon_func 進程pid=3772,ppid=24588 ### 這里ppid是24588,而不是1,說明主進程還未中止
(4.1)守護進程被join:
# -*- coding:utf-8 -*- from multiprocessing import Process import time import os # 守護進程p1執行 def daemon_func(): time.sleep(1) print('daemon_func 進程pid={},ppid={}'.format(os.getpid(), os.getppid())) # 非守護進程p2執行 def non_daemon_func(): time.sleep(2) print('non_daemon_func 進程pid={},ppid={}'.format(os.getpid(), os.getppid())) if __name__ == "__main__": print('主進程id {}'.format(os.getpid())) p1 = Process(target=daemon_func) p1.daemon = True p2 = Process(target=non_daemon_func) p1.start() p2.start() p1.join() # join守護進程
主進程執行完后沒中止守護進程,並等待非守護進程執行完:
主進程id 24416
daemon_func 進程pid=27312,ppid=24416
non_daemon_func 進程pid=12408,ppid=24416
守護進程睡三秒,主進程仍會等待守護進程執行完:
主進程id 20336
non_daemon_func 進程pid=24528,ppid=20336
daemon_func 進程pid=16596,ppid=20336
守護進程被join后,主進程會等待守護進程執行完。因此,join守護進程,還不如直接開非守護進程
(4.2)非守護進程被join
# -*- coding:utf-8 -*- from multiprocessing import Process import time import os # 守護進程p1執行 def daemon_func(): time.sleep(1) print('daemon_func 進程pid={},ppid={}'.format(os.getpid(), os.getppid())) # 非守護進程p2執行 def non_daemon_func(): time.sleep(2) print('non_daemon_func 進程pid={},ppid={}'.format(os.getpid(), os.getppid())) if __name__ == "__main__": print('主進程id {}'.format(os.getpid())) p1 = Process(target=daemon_func) p1.daemon = True p2 = Process(target=non_daemon_func) p1.start() p2.start() p2.join() # join非守護進程
非守護進程被join,主進程等待非守護進程,非守護進程中止后,立即中止守護進程。
------------------------------------------------------------------------------ 注意【1.主進程只join非守護進程】和【2.主進程沒有join任何進程】的區別:
1: 主進程會被join阻塞,等到非守護進程中止后,中止守護進程
2: 主進程執行完立即中止守護進程,若非守護進程未中止,等待
其實都是主進程的代碼執行完畢后,才中止子守護進程,只是1包含了手動join進程的代碼,阻塞后代碼才算執行完畢。
-----------------------------------------------------------------
守護進程睡1秒后,非守護進程還在睡,主進程被阻塞住,因此守護進程有輸出
主進程id 11284
daemon_func 進程pid=5308,ppid=11284
non_daemon_func 進程pid=30364,ppid=11284
守護進程睡3秒后,非守護進程在2秒時睡醒后中止,主進程便在此時中止守護進程,因此守護進程沒有輸出
主進程id 24912
non_daemon_func 進程pid=19892,ppid=24912
join在很多地方都有用到,預知join的行為並合理利用join,避免產生死鎖
terminate()
中止進程,在Unix上是用SIGTERM信號完成的;在Windows上,使用TerminateProcess();
注意,退出處理程序和finally子句等不會被執行
被中止進程的子進程不會被中止
避免使用此方法:使用該方法停止進程可能導致進程當前使用的任何共享資源被破壞或不可用於其它進程,最好只考慮該方法用在從不使用共享資源的進程上
參考:
如有意見或建議,一起交流;如有侵權,請告知刪除。