if __name__=="__main__" 語句在調用多進程Process過程中是必須的,否則會報如下錯誤An attempt has been made to start a new process before the current process has finished its boo... ...
2018年2月27日 於創B515
引言
最近準備學習一下如何使用Python中的多進程。在翻看相關書籍、網上資料時發現所有代碼都含有if __name__=="__main__",在實驗的過程中發現如果在運行代碼過程中,沒有這句話Python解釋器就會報錯。雖然Python對於multiprocessing的文檔第17.2.1.1節中【1】提到必須如此使用,但是我覺得並沒有根本上解釋清楚。因此我決定從源碼來解釋我的疑惑。
# 代碼0.1錯誤代碼
import multiprocessing as mp import os def do(): print("pid is : %s ..." % os.getpid()) print("parent id is : %s ..." % os.getpid()) p = mp.Process(target=do, args=()) p.start()
# 代碼0.2正確代碼 import multiprocessing as mp import os def do(): print("pid is : %s ..." % os.getpid()) if __name__ == '__main__': print("parent id is : %s ..." % os.getpid()) p = mp.Process(target=do, args=()) p.start()
問題描述
問題
在運行代碼-0.1時,會出現RuntimeError,錯誤提示如下。但是運行代碼0.2時就不會,一切順利。
An attempt has been made to start a new process before the current process has finished its bootstrapping phase. This probably means that you are not using fork to start your child processes and you have forgotten to use the
proper idiom in the main module: if __name__ == '__main__': freeze_support() ... The "freeze\_support()" line can be omitted if the program is not going to be frozen to produce an executable.
問題產生的環境
運行環境: | Win10 |
IDE | Sublime Text3 |
簡單解釋
由於Python運行過程中,新創建進程後,進程會導入正在運行的文件,即在運行代碼0.1的時候,代碼在運行到mp.Process時,新的進程會重新讀入改代碼,對於沒有if __name__=="__main__"保護的代碼,新進程都認為是要再次運行的代碼,這是子進程又一次運行mp.Process,但是在multiprocessing.Process的源碼中是對子進程再次產生子進程是做了限制的,是不允許的,於是出現如上的錯誤提示。
詳細解釋
先談一談if__name__=="__main__"
在Python有關__main__的文檔中【2】說明“__main__”是代碼執行時的最高的命名空間(the name of the scope in which top-level code executes),當代碼被當做腳本讀入的時候,命名空間會被命名為“__main__”,對於在腳本運行過程中讀入的代碼命名空間都不會被命名為“__main__”。這也就是說創建的子進程是不會讀取__name__=="__main__"保護下的代碼。
再談一談multiprocessing(win32下的源碼分析)
multiprocessing根據平臺不同會執行不同的代碼:在類UNIX系統下由於操作系統本身支持fork()語句,win32系統由於本身不支持fork(),因此在兩種系統下multiprocessing會運行不同的代碼,如圖1 UNIX平臺、圖2 win32平臺(包含在context.py文件中,Process的定義也是在context.py文件中)。
圖1 對於類UNIX系統平臺
圖2 對於win32系統平臺
Process是一個高度依賴繼承的類———其父類是BaseProcess,如圖3 Process類的定義。在使用過程中先初始化一個Process實例,然後通過Process.start()來啟動子進程。我們繼續看關於Process.start()的定義,如圖4 BaseProcess類中的start()定義。在其中第105行,調用了self._Popen(self),該函數重定義定義於Process類。該函數按調用順序最後會跳轉到 popen_spwan_win32.py 文件中的Popen類,如圖5 Popen類的定義。Popen類在初始話過程中首先調用windows相關介面,生成一個管道(38行),取得對管道讀寫的句柄,然後生成一個命令行的字元串列表——cmd,如下:
['C:\\Program Files\\Python35\\python.exe', '-B', '-c', 'from multiprocessing.spawn import spawn_main;spawn_main(pipe_handle=928, parent_pid=9292)', '--multiprocessing-fork']
圖3 Process類的定義
圖4 BaseProcess類中的start()定義
圖5 Popen類的定義
這個字元串列表之後通過命令行送入系統,得到相應的子進程和子線程的句柄及子進程和子線程的ID號。在第65、66行是通過pickle方法將必要的數據從父進程傳輸給子進程。新進程是調用spawn.py文件中的spawn_main函數,如圖6 spawnmain的定義。其中第100行的steal_handle()的作用是子進程獲取父進程生成的句柄,用於後續通信——使用pickle方法從父進程中讀取必要數據。而問題的出現就是在這之後出現!該代碼在第106行調用_main(),其次在第115行調用prepare(),再次運行到223行時,運行_fixup_main_from_name(),而此時該函數會運行父進程的腳本。因此對於沒有if__name__=="__main__"保護的代碼都是要運行的,而此時在第二次運行Process創建新進程的時候在第123行 if getattr(process.current_process(), '_inheriting', False): 時,由於子進程是具有_inheriting屬性,因此會激發出上述錯誤代碼。
圖6 spawn_main的定義
對於代碼中生成新子進程時所用到的兩個技術我覺很有趣也很苦惱,因此決定繼續看下去。下麵簡單描述一下兩個技術的概況。
—— pickle模塊
首先是pickle模塊。pickle模塊是一個Python中特有的數據格式,與JSON等不同,是不能被其他語言識別的格式。在Python中的官方文檔【3】中比較了pickle模塊與JSON的不同之處,以及介紹了pickle的使用條件。簡單摘錄如下:
|
|
—— 管道(Pipe)
這裡理解管道是從類UNIX系統理解,因為其理解起來更方便。管道在類UNIX系統中也是一種文件,在生成新的子進程的時候將兩個進程都關聯至同一個管道上,這樣就是雙工通信(父子進程相互可讀可寫)。如果要實現單工通信,就關閉相應的通道(一方寫一方讀)【4】。
參考文獻
- Python3.5 關於multiprocessing的文檔 https://docs.python.org/3.5/library/multiprocessing.html
- Python3.5 關於\_\_main\_\_的文檔 https://docs.python.org/3.5/library/__main__.html
- Python3.5 關於pickle的文檔 https://docs.python.org/3.5/library/pickle.html?highlight=pickle#module-pickle
- 網上一片關於pipe的介紹 http://www.cnblogs.com/qiaoyanlin/p/7576085.html