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