urllib.request 模組的官方文件
在 macOS 上使用 urllib.request 模組與 os.fork() 的問題主要與多程序程式設計和系統 API 的互動有關。具體來說,urllib.request 模組在獲取代理設定時會呼叫 macOS 的系統級函式來獲取這些資訊,而這些函式可能並不是“fork-safe”的。
當你在 Python 中使用 os.fork() 建立一個新的子程序時,子程序會繼承父程序的記憶體空間和檔案描述符。在 fork() 呼叫之後,父子程序中的記憶體是分離的,但是在 fork() 呼叫之前已經開啟的資源(例如檔案描述符和鎖)會在父子程序間共享。
某些系統級別的 API,包括 macOS 的 getproxies() 函式,可能在內部使用了不適用於多程序場景的資源,比如全域性鎖或者背景執行緒。如果你在父程序中呼叫了這些 API,然後進行了 fork(),子程序可能會試圖使用已經由父程序獨佔或改變狀態的資源,這可能導致競態條件、死鎖或其他不可預測的行為。
Apple 的文件明確指出了一些不應該在多執行緒或多程序環境中使用的 API。此外,POSIX 標準也指出 fork() 後唯一安全呼叫的函式是 exec() 系列函式,因為許多庫和系統呼叫在 fork() 後可能不安全。
為了避免這種型別的問題,你應該儘量避免在多程序應用程式中使用可能不是 fork-safe 的系統 API,或者在 fork() 之後不要呼叫任何可能會與父程序中開啟的資源發生衝突的程式碼。
如果你在多程序程式中確實需要使用 urllib.request,有以下幾種方法可以減少問題發生的可能性:
- 在 fork() 之前初始化任何可能會呼叫系統 API 的庫或模組;
- 使用 multiprocessing 模組代替 os.fork(),因為它提供了更高層次的抽象,並且在建立新程序時會更加小心地管理資源;
- 如果你的程式結構允許,儘量避免在多程序中進行網路請求,或者在 fork() 之前完成所有網路請求。
參考:
https://docs.python.org/zh-cn/3/library/urllib.request.html
https://bugs.python.org/issue30385