1 場景說明
在Python中,使用multiprocessing
模塊創建子進程時,將創建子進程的代碼放在if __name__ == '__main__':
塊之外,如下面代碼:
import multiprocessing
import timedef test_func(name):print(f"子進程 {name} 開始運行")time.sleep(2) # 模擬任務執行print(f"子進程 {name} 結束運行")# 創建子進程
p = multiprocessing.Process(target=test_func, args=("1",))
# 啟動子進程
p.start()
# 等待子進程結束
p.join()
print("主進程結束")
2 問題描述
代碼執行過后,報錯信息如下:
raise RuntimeError('''
RuntimeError: An attempt has been made to start a new process before thecurrent process has finished its bootstrapping phase.This probably means that you are not using fork to start yourchild processes and you have forgotten to use the proper idiomin the main module:if __name__ == '__main__':freeze_support()...The "freeze_support()" line can be omitted if the programis not going to be frozen to produce an executable.To fix this issue, refer to the "Safe importing of main module"section in https://docs.python.org/3/library/multiprocessing.html
3 原因分析
在 Python 中,使用 multiprocessing 模塊創建子進程時,必須將創建子進程的代碼放在if __name__ == '__main__':
塊中。這是因為在 Windows 和 macOS 上,Python 使用spawn
方式啟動子進程,這種方式會重新導入主模塊。如果不將代碼放在 if __name__ == '__main__':
塊中,會導致子進程重新執行主模塊的代碼,從而引發遞歸創建子進程的問題。
4 解決方案
將創建子進程的代碼塊放到if __name__ == '__main__':
中
import multiprocessing
import timedef test_func(name):print(f"子進程 {name} 開始運行")time.sleep(2) # 模擬任務執行print(f"子進程 {name} 結束運行")if __name__ == '__main__':# 創建子進程p = multiprocessing.Process(target=test_func, args=("1",))# 啟動子進程p.start()# 等待子進程結束p.join()print("主進程結束")
5 Multiprocessing模塊中子進程啟動方式
5.2 啟動方式說明
在 Python 的 multiprocessing 模塊中,子進程的啟動方式主要有三種:spawn
、fork
和 forkserver
。不同的操作系統默認使用不同的啟動方式,而這些方式的行為和適用場景也有所不同。
5.3 默認啟動方式
Windows: 默認使用 spawn
。
macOS(Python 3.8+): 默認使用 spawn
。
Linux/Unix: 默認使用 fork
。
5.4 設置啟動方式
import multiprocessingdef worker():print("子進程運行")if __name__ == '__main__':# 設置啟動方式,可選spawn、fork、forkservermultiprocessing.set_start_method('spawn')# 如果設置成fork,則會拋異常ValueError: cannot find context for 'fork'# 創建子進程p = multiprocessing.Process(target=worker)p.start()p.join()
5.5 如何選擇啟動方式
Windows/macOS: 默認使用 spawn,無需更改。
Linux/Unix:
如果需要快速啟動且不涉及多線程,使用 fork。
如果需要避免資源沖突,使用 forkserver 或 spawn。
5.6 注意事項
5.6.1 多線程與 fork
在 Linux 上,如果父進程中有多線程,使用 fork 可能會導致死鎖或資源沖突。在這種情況下,建議使用spawn
或 forkserver
。
5.6.2 跨平臺兼容性
如果代碼需要在多個平臺上運行,建議使用spawn
,因為它是 Windows 和 macOS 的默認方式。
5.7 啟動方式比較
啟動方式 | spawn | fork | forkserver |
---|---|---|---|
描述 | 重新啟動一個 Python 解釋器,只繼承主進程中運行進程所需的資源。不會繼承父進程的內存狀態。 | 直接復制父進程的所有資源(包括內存狀態),創建子進程。 | 先啟動一個服務器進程,之后每次創建子進程都從該服務器進程 fork。 |
支持操作系統 | Windows、macOS | Linux、Unix | Linux、Unix |
優點 | 安全性高,避免了父進程狀態不一致的問題。 | 啟動速度快,因為直接復制父進程狀態。 | 避免了直接 fork 父進程可能導致的問題。 比 spawn 更快,因為服務器進程已經初始化。 |
缺點 | 啟動速度較慢,因為需要重新導入模塊和初始化環境。 | 可能導致資源沖突(如文件描述符、鎖等)。 在 macOS 上,fork 可能會導致問題(尤其是涉及多線程時)。 | 需要額外的服務器進程。 |
適用場景 | Windows 和 macOS(默認方式)。 需要避免共享父進程狀態的場景。 | Linux/Unix(默認方式)。 需要快速啟動子進程且不涉及多線程的場景。 | Linux/Unix。 需要頻繁創建子進程且希望避免資源沖突的場景。 |