gpt4 book ai didi

python - 在工作进程启动之前将数据放入输入队列时,工作进程在 requests.get() 上崩溃

转载 作者:太空狗 更新时间:2023-10-30 01:17:59 24 4
gpt4 key购买 nike

在 macOS High Sierra(版本 10.13.6)中,我运行了一个执行以下操作的 Python 程序:

  • 启动一个工作进程,该进程使用来自 multiprocessing.Queue 的数据(URL 字符串)。 .
  • 工作进程发送带有 requests 的 HTTP 请求包,即它使 requests.get()调用。
  • 一些数据(一个 URL 字符串)甚至在工作进程启动之前就被馈送到队列中。

  • 满足上述条件的程序会导致工作进程因以下错误而崩溃:
    objc[24250]: +[__NSPlaceholderDate initialize] may have been in progress in another thread when fork() was called.
    objc[24250]: +[__NSPlaceholderDate initialize] may have been in progress in another thread when fork() was called. We cannot safely call it or ignore it in the fork() child process. Crashing instead. Set a breakpoint on objc_initializeAfterForkError to debug.

    我已阅读以下主题:
  • Multiprocessing causes Python to crash and gives an error may have been in progress in another thread when fork() was called
  • Requests module crashes python when numpy is loaded and using process
  • Rails: may have been in progress in another thread when fork() was called

  • 这些线程专注于用户的解决方法。解决方法是定义此环境变量:
    OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES

    在这个问题中,我想了解为什么只有某些条件会重现错误而其他条件不会,以及如何在不承担定义环境变量的负担的情况下解决此问题 OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES在用户上。

    问题的最小示例
    import multiprocessing as mp
    import requests


    def worker(q):
    print('worker: starting ...')

    while True:
    url = q.get()
    if url is None:
    print('worker: exiting ...')
    break

    print('worker: fetching', url)
    response = requests.get(url)
    print('worker: response:', response.status_code)


    def master():
    q = mp.Queue()
    p = mp.Process(target=worker, args=(q,))
    q.put('https://www.example.com/')

    p.start()
    print('master: started worker')

    q.put('https://www.example.org/')
    q.put('https://www.example.net/')
    q.put(None)
    print('master: sent data')

    print('master: waiting for worker to exit')
    p.join()
    print('master: exiting ...')


    master()

    这是带有错误的输出:
    $ python3 foo.py 
    master: started worker
    master: sent data
    master: waiting for worker to exit
    worker: starting ...
    worker: fetching https://www.example.com/
    objc[24250]: +[__NSPlaceholderDate initialize] may have been in progress in another thread when fork() was called.
    objc[24250]: +[__NSPlaceholderDate initialize] may have been in progress in another thread when fork() was called. We cannot safely call it or ignore it in the fork() child process. Crashing instead. Set a breakpoint on objc_initializeAfterForkError to debug.
    master: exiting ...

    决议

    以下是我看到的一些解决问题的独立事情,即仅执行其中一项即可解决问题:
  • 该问题似乎仅在使用 requests 时发生包裹。如果我们在 worker() 中注释掉这两行,它解决了这个问题。
        # response = requests.get(url)
    # print('worker: response:', response.status_code)
  • 该问题似乎仅在 q.put('https://www.example.com/') 时才会发生语句出现在 p.start() 之前陈述。如果我们在 p.start() 之后移动该声明,这解决了问题。
        p.start()
    print('master: started worker')

    q.put('https://www.example.com/')
  • 设置环境变量 OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES解决了这个问题。
    OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES python3 foo.py

  • 非分辨率

    现在,我不希望我的用户设置这样的变量名称以便能够使用我的工具或 API,所以我试图确定在我的程序中设置这个环境变量是否可以解决问题。我发现将其添加到我的代码中并不能解决问题:
    import os
    os.environ['OBJC_DISABLE_INITIALIZE_FORK_SAFETY'] = 'YES'
    # Does not resolve the issue!

    问题
  • 为什么这个问题只会在给定的条件下发生,即 requests.get()q.put()之前 p.start() ?换句话说,如果不满足这些条件之一,为什么问题会消失?
  • 如果我们将最小示例之类的东西公开为另一个开发人员可能从他们的代码中调用的 API 函数,是否有任何聪明的方法可以在我们的代码中解决这个问题,以便其他开发人员不必设置 OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES在运行使用我们函数的程序之前在他们的 shell 中?

  • 当然,一个可能的解决方案是重新设计解决方案,这样我们就不必在工作进程启动之前将数据馈送到队列中。这绝对是一个可能的解决方案。不过,这个问题的范围是讨论为什么只有在工作进程启动之前将数据馈入队列时才会出现此问题。

    最佳答案

    很好的问题描述!你得到了我的赞成。

    现在回答:

  • 在 macOS 10.13 之前,objective-C 运行时不支持在 fork() 之间使用和 exec()在多线程父进程的子进程中。您不能在该时间间隔内调用任何 Objective-C 方法。这会导致竞争条件。即大多数时候它会工作,有时它会失败。例如:如果 fork() 时,父进程中的线程碰巧持有 Object-C 运行时的锁之一。发生时,子进程在尝试获取该锁时会死锁。
  • 从 macOS 10.13 开始,Objective-C 运行时现在支持使用“介于”fork()exec() .但是,存在涉及 +initialize 的限制。方法。 (你的问题是在这个区域)。

  • 现在,在提出解决方案之前。让我谈谈与 fork 相关的复杂性:
  • fork创建过程的副本。
  • 子进程使用 execve() 将自身替换为不同的程序系统调用

  • 到目前为止一切似乎都还不错吧?子进程(在您的情况下为 worker)具有父进程的副本,并且该副本由 fork() 提供给子进程。 .但是, fork()不会复制所有内容!特别是,它不复制线程。子进程中不存在父进程中运行的任何线程

    在这一点上,关注你的问题:

    虽然,macOS 10.13+ 支持在 fork 之间做“任何事情”和 exec .然而,在 fork之间做任何事情都是非常不正确的。和 exec .在您的情况下,请调用 q.put()之前 p.start()正如@Darkonaut 正确提到的那样,第一次调用时会启动一个馈线线程,并且 fork 已经是多线程的应用程序是有问题的。

    这是因为 +initialize方法仍然有限制 fork() .问题在于 +initialize的线程安全保证在 Objective-C 运行时无法控制的状态周围隐式引入锁。

    当您调用 q.put()或使用 requests库(调用流行的请求库,这将最终调用 _scproxy 模块以获取系统代理,这将最终调用 +initialize 方法)在 p.start() 之前,它们中的任何一个都会导致您的父进程获取锁。您必须注意 fork创建进程的副本。在你的情况下,当 q.put()p.start() 之前被调用, fork发生在错误的时间,而您是 workers谁得到父进程的副本,得到 lock处于复制状态。

    在你 worker , 你在做 q.get() .这意味着获取锁,但是在 fork期间已经获取了锁(来自 parent )。

    子进程 ( worker ) 等待 lock被释放但 lock永远不会被释放。因为,释放它的线程没有被 fork() 复制过来。 .

    没有什么好办法做 +initialize线程安全和 fork 安全。相反,Objective-C 运行时只是停止进程而不是运行任何 +initialize在子进程中覆盖:
    +[SomeClass initialize] may have been in progress in another thread when fork() was called. We cannot safely call it or ignore it in the fork() child process. Crashing instead.

    希望能回答您的问题 1。

    现在,对于问题 2:

    一些从好到坏的解决方法:
  • fork()之间什么也不做和 exec() (最好不要在 fork()exec*() 之间使用请求)。
  • 在 fork() 和 exec() 之间仅使用异步信号安全操作。可用函数列表 here
  • 定义环境变量 OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES,或添加 __DATA,__objc_fork_ok 部分,或使用早于 macOS 10.13 的 SDK 构建。然后交叉你的手指。
  • 关于python - 在工作进程启动之前将数据放入输入队列时,工作进程在 requests.get() 上崩溃,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55924761/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com