curl_multi_remove_handle 的线程安全

Thread safety of curl_multi_remove_handle

似乎有些消息来源建议使用 curl_multi_remove_handle 到 "invalidate" 卷曲句柄,并导致 curl_multi_wait 到 return 早。这似乎不在线程安全保证的范围内(如果从另一个线程完成),或者我错了(线程安全保证基本上只是重入保证)?

什么 提早向 curl_multi_wait 发出信号 return 的推荐方式?真的需要通过超时来完成吗? (在 Linux 下,我会在 epoll 集中使用一个 eventfd 来有效地处理 "wait on these sockets OR this event fd OR the given timeout" 的情况。)似乎我可以使用自定义 curl_waitfd 结构,但这需要特定于平台的设置虚拟插座。

不能 从线程 B 调用 curl_multi_remove_handle 如果 curl_multi_wait 的句柄是 运行 在线程 A 中。那只会导致眼泪和痛苦。

您可以选择,例如:

  • 用户 curl_multi_wait() 的超时足够短,这样您就不需要中止它
  • 添加一个私有 socket/file 描述符以发送数据以在需要时中止
  • return 您需要停止的传输的进度回调(或另一个回调)错误 - 通过设置一个他们都检查的标志(全局或类似全局)
  • 重做你的应用程序逻辑,这样你就可以考虑转移到“死”而它还没有停止,让 libcurl 有它的原因并稍后关闭它,你不必太在意它是否已经完成在你决定可以忽略它之后。

curl_multi_poll()

在我第一次写这个答案后,我们引入了curl_multi_poll in libcurl. This function is very similar to curl_multi_wait but also allows it to pre-emptively return with the use of curl_multi_wakeup,从而为应用程序提供了更多的替代方法。

不幸的是,curl_multi 不是当今人们认为的“线程安全”。是的,您可以在两个不同的线程中使用 CURLM 句柄,只要它们不同时访问它即可。但是,嘿,这几乎适用于 C 或 C++ 中的任何数据结构。

因此,如果您有一个线程 运行 一个带有 curl_multi_wait() 的事件循环,您不能使用第二个线程通过 curl_multi_add_handle() 添加新作业或通过 [=14 删除作业=].好吧,它在大多数情况下都能正常工作,但尤其是在高负载期间,由于并发访问 libcurl 的内部数据结构,您将开始出现数据损坏和段错误。

有两种方法可以解决这个问题,但都需要一些编码:

  • 使用较新的 curl_multi_poll() 界面,它(与 curl_multi_wait() 不同)可通过 curl_multi_wakeup() 从外部中断。是的,curl_multi_wakeup()CURLM 句柄上的唯一函数,可以安全地从另一个线程(甚至多个线程)并发调用。要向事件循环添加新请求或从中删除请求,您需要一些请求队列和互斥锁,以确保对该队列的访问。然后,要添加新工作,您可以这样做:

    • (线程1是运行curl_multi_poll()死循环)
    • 线程 2 获取所述互斥量
    • 线程 2 post将“添加简单处理请求”添加到请求队列中
    • 线程 2 再次释放所述互斥量
    • 线程 2 调用 curl_multi_wakeup()
    • 线程1在curl_multi_poll()returns
    • 之后获取互斥量
    • 线程1然后处理作业列表中的“添加easy handle请求”并执行curl_multi_add_handle()
    • 线程 1 然后再次释放互斥量
    • 线程 1 完成所有其他必要的工作(特别是调用 curl_multi_perform() 并将完成的传输传递给应用程序等)
    • 线程 1 再次调用 curl_multi_poll()

    要删除作业,您可以使用相同的过程,只需让线程 2 post 向请求队列发送“删除简单处理请求”而不是“添加简单处理请求”,然后让线程1 调用 curl_multi_remove_handle() 而不是 curl_multi_add_handle().

    在此解决方案中,对 CURLM 句柄的所有调用都是从线程 1 执行的,唯一的例外是 curl_multi_wakeup(),其他线程使用它来通知线程 1 正在等待新工作在请求队列中。

  • 或使用 curl_action() 接口,您必须在其中向 libcurl 提供两个回调,它会报告要监视的文件描述符和应用程序的超时。然后你必须自己调用 epoll() 或类似的 OS 函数来等待事件循环线程中的 activity (或超时)。然后再次添加一个互斥锁以序列化对 CURLM 句柄的访问:您的事件循环线程应该在调用 curl_action()(或 CURLM 句柄上的任何其他函数)之前锁定该互斥锁并解锁它紧随其后。由于 curl_action()(与 curl_multi_poll() 不同)不会休眠,因此该互斥体只会在很短的时间内被锁定。因此其他线程也可以轻松地直接为自己锁定该互斥锁,并根据需要调用 curl_multi_add_handle()curl_multi_remove_handle() 。但是请注意,那些干预句柄的添加或删除可以修改活动的 FD 集,并且您可能需要与事件循环线程进行一些同步以通知它已修改的 epoll() 集。

第一个解决方案可能更容易实施。您应该能够在 Github 上找到这两个变体的 libcurl 包装器,但一定要在任何关键应用程序中使用它们之前对它们进行深入测试。