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 包装器,但一定要在任何关键应用程序中使用它们之前对它们进行深入测试。
似乎有些消息来源建议使用 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 正在等待新工作在请求队列中。- (线程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 包装器,但一定要在任何关键应用程序中使用它们之前对它们进行深入测试。