ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

curl 线程池与线程队列(Curl_thrdpool / Curl_thrdq)源码级解析:异步工作项处理的并发骨架

curl 线程池与线程队列(Curl_thrdpool / Curl_thrdq)源码级解析:异步工作项处理的并发骨架 curl 线程池与线程队列Curl_thrdpool / Curl_thrdq源码级解析异步工作项处理的并发骨架【免费下载链接】curlA command line tool and library for transferring data with URL syntax, supporting DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS and WSS. libcurl offers a myriad of powerful features项目地址: https://gitcode.com/GitHub_Trending/cu/curl本篇技术指南围绕 curl 内部文档 docs/internals/THRDPOOL-AND-QUEUE.md 展开深入剖析 libcurl 中用于异步处理工作项work item的两大基础设施——线程池Curl_thrdpool与线程队列Curl_thrdq。读完本文你将掌握其核心数据结构、三个回调函数的职责划分、并发安全约定、生命周期管理规则以及它们如何被用于 curl 的多线程 DNS 解析器lib/vdns/asyn-thrdd.c这一真实生产场景。概述为什么 curl 需要线程池与线程队列curl 的 libcurl 主体以单线程事件驱动模型multi 接口运行但某些操作天然是阻塞式的——最典型的就是系统getaddrinfo()域名解析。为了不让一次慢 DNS 查询阻塞整个 multi 事件循环curl 在支持线程的构建中pthreads或原生 Win32 线程引入了异步线程处理机制。线程池Curl_thrdpool与线程队列Curl_thrdq正是这套机制的两层骨架线程池负责有多少个线程在跑、线程如何领取任务、空闲超时后如何退出线程队列在池之上再封装一层发送队列 接收队列让调用方可以安全地投递工作项并取回处理结果。两者对工作项本身完全透明——工作项对池与队列而言只是一个void *具体内容与语义由调用方通过回调函数定义。源码层面二者分别实现在 lib/thrdpool.c、lib/thrdpool.h 与 lib/thrdqueue.c、lib/thrdqueue.h。需要特别说明的是线程特性在 curl 中的启用前提从 lib/curl_setup.h 可以看到USE_THREADS宏仅在USE_RESOLV_THREADED线程化 resolver 构建时被定义也就是说当前仓库中线程池与线程队列的唯一驱动者就是多线程 DNS 解析器。文档 docs/internals/THRDPOOL-AND-QUEUE.md 所指的 available with pthreads or native Win32 builds 即对应这一点。Curl_thrdpool管理一组异步工作线程Curl_thrdpool是管理线程池的数据结构负责线程的创建、唤醒、休眠与销毁。池的属性Properties一个线程池具备三个核心属性参见 lib/thrdpool.h 中Curl_thrdpool_create的参数属性参数默认值说明常驻线程数下限min_threads0池创建时立即启动这么多线程并保证它们始终运行线程数上限max_threads必填池内最多同时运行的线程数空闲线程超时idle_time_ms0空闲线程超过该毫秒数后退出仅针对超过最小值的多余线程0表示无限期等待创建时池会先启动min_threads个线程当有工作到来而现有线程都忙时按需启动新线程直至max_threads当工作停止后超出最小值的线程会在空闲超时后自行退出。源码中struct curl_thrdpoollib/thrdpool.c将这些属性与回调函数、互斥锁、条件变量、slots运行线程链表与zombies待回收线程链表组织在一起。值得注意的细节工作线程的循环体thrdslot_runlib/thrdpool.c只有在线程数大于最小值时才会使用带超时的Curl_cond_timedwait否则使用普通的Curl_cond_wait——源码注释明确说明这是为了避免短暂的空闲超时反复激活那些根本无法退出的线程。操作Operation三个回调函数池创建时需要提供三个回调函数它们构成了池与外部世界的全部契约take池线程调用它以领取下一个工作项。从池的视角看工作项就是一个void *。take返回非NULL时该线程进入忙碌busy状态开始处理返回NULL时线程转为空闲idle。在头文件 lib/thrdpool.h 中take回调的实际签名比文档描述更丰富void *Curl_thrdpool_take_item_cb(void *user_data, const char **pdescription, timediff_t *ptimeout_ms)——它还可以输出工作项的描述字符串供CURLVERBOSE跟踪日志使用其生命周期须与工作项一致以及该项的工作超时。process由池线程调用以处理工作项。该回调不能返回错误——任何错误处理都必须通过工作项自身的属性来完成池对此一无所知。在实现中process是唯一在解锁状态下被调用的回调见下文安全节这保证了耗时处理不占用池的互斥锁。return处理完成后工作项被归还return池从此不再持有对它的任何引用。操作唤醒与销毁池不会主动去领取工作它只在被明确告知时尝试takeCurl_thrdpool_signal(pool, n)唤醒最多n个线程去领取新工作项这可能导致启动新线程lib/thrdpool.c 的thrdpool_signal内部先唤醒空闲线程若仍不足且在max_threads以内则调用thrdslot_start创建新线程另一个take时机是池线程处理完并归还上一项之后立即尝试领取下一项。销毁通过Curl_thrdpool_destroy(pool, join)完成其中join决定活动线程是被join等待其结束还是被detach分离。实现细节lib/thrdpool.c销毁时置aborted标志并唤醒所有线程join模式下循环等待所有slots清空随后回收zombies分离模式下则对仍在运行的线程调用Curl_thread_destroy并标记detached。池还通过引用计数refcount管理自身生命周期——当最后一个线程退出时thrdpool_unlink池对象才被真正释放。安全Safety互斥锁与条件变量线程池通过一个互斥锁Curl_mutex_t lock与条件变量管理并发所有交互与回调调用都在池的锁内进行唯独process回调在锁外调用参见 lib/thrdpool.c 的thrdslot_run在锁内take解锁后process重新加锁后return为避免死锁任何回调都不得再调用池的其他函数——因为任何池函数的调用都可能触发回调的再次进入工作项一旦被池领取其他任何地方都不应再持有它的引用线程池始终会对工作项调用return回调即使池已通过分离线程的方式被销毁也不例外。user_data的销毁语义一个精巧的约定池创建时传入的user_data会传递给take与return回调。一旦池被销毁user_data被清空此后return回调看到的user_data恒为NULL——这使return回调可以据此判断池已被销毁从而自行负责释放工作项资源。这一约定在 lib/thrdpool.c 的tpool-fn_return(item, tpool-aborted ? NULL : tpool-fn_user_data)中落地也在头文件注释lib/thrdpool.h与销毁注释lib/thrdpool.h中明确记载。队列层正是利用这一点在池销毁后回收尚未归还的工作项见下文。补充接口除文档所述外池还提供了若干辅助接口lib/thrdpool.hCurl_thrdpool_await_idle(pool, timeout_ms)阻塞等待池内所有线程进入空闲内部通过轮询thrdpool_all_idle与Curl_cond_timedwait实现lib/thrdpool.cCurl_thrdpool_set_props(pool, min, max, idle)动态调整池属性max_threads为 0 或min max时返回CURLE_BAD_FUNCTION_ARGUMENT若新min_threads大于当前运行线程数还会自动补足线程lib/thrdpool.cCurl_thrdpool_trace(pool, data)在CURLVERBOSE构建下输出每个线程槽位id、starting / idle / busy 状态、已运行毫秒数、工作描述的跟踪信息lib/thrdpool.c。Curl_thrdq带线程池的双向工作队列Curl_thrdq是一个双向队列 线程池的组合体使用者通过Curl_thrdq_send()把工作项发送进队列通过Curl_thrdq_recv()把处理完的项接收回来。属性Properties内部要创建的线程池的全部属性min_threads/max_threads/idle_time_ms发送队列的最大长度0表示不限对应Curl_thrdq_send的timeout_ms语义见下文。操作Operation三个回调函数队列创建时同样需要三个回调lib/thrdqueue.hfree释放一个仍在队列中但不再会被归还或不再会被处理的工作项——发生在队列被销毁时或因其他原因被移出时。头文件明确指出free可能在任何线程、任何时刻被调用用于处理入队前或处理后的项lib/thrdqueue.hprocess处理队列项同样不能失败event当有工作项被加入接收列表receive list时被调用。该回调是可选的创建时传NULL且会在队列不加锁的状态下、从任意线程被调用lib/thrdqueue.h。操作send 与 recvCurl_thrdq_send(tqueue, item, description, timeout_ms)把工作项送入发送队列。注意三点语义成功后调用方必须放弃对工作项的所有引用——队列接管所有权description是可选的跟踪描述字符串其生命周期须与工作项一致timeout_ms ! 0时在发送队列中滞留超过该时长的项会被立即移入接收队列视为超时返回此时它们不会被执行process而是直接作为已就绪结果交还调用方。该逻辑在 lib/thrdqueue.c 的thrdq_tpool_take中体现take遍历发送队列时计算timeout_ms - 已等待时长若为负则直接Curl_llist_append到recvq并触发event回调。Curl_thrdq_recv(tqueue, pitem)取回最旧的一个已处理项调用方随之取得所有权队列放弃引用。当没有已处理项时返回CURLE_AGAIN并把pitem置为NULL此时若仍有项在等待发送队列会再次 signal 线程池确保存在一个工作线程去处理它们——这是对此前线程启动可能失败的补偿机制lib/thrdqueue.c。队列对线程池的桥接三个内部桥接回调队列创建时会把自己的内部函数作为回调传给线程池lib/thrdqueue.c从而把池的 take/process/return 契约翻译为队列的 send/recv 契约thrdq_tpool_take从发送队列头部取出一个未超时的项交给池线程同时把超时项转入接收队列并在锁外触发event避免死锁lib/thrdqueue.cthrdq_tpool_process解包后调用用户的processlib/thrdqueue.cthrdq_tpool_return把处理完的项追加到接收队列并触发event若池已销毁user_data NULL正是上文user_data销毁约定的体现则直接调用free释放该项lib/thrdqueue.c。由此工作项的生命周期路径为send 队列 →池线程 take→ process锁外→ return → recv 队列 → 调用方 recv 取得所有权。安全Safety与线程池一致队列也使用互斥锁 条件变量管理并发所有交互与回调调用都在队列锁内进行唯独process在锁外调用使用者不得保留任何已发送进队列的工作项引用由工作项分配的资源必须由free回调负责释放它在销毁、清除、池分离等场景下都会被调用。补充接口Curl_thrdq_check_started(tqueue)检查等待处理的工作项是否已有工作线程若没有则 signal 池去启动一个——用于补偿上次线程启动恰好失败的情形返回FALSE表示线程启动再次失败调用方应尽快重试lib/thrdqueue.hCurl_thrdq_clear(tqueue, fn_match, match_data)按匹配回调清除发送/接收队列中所有匹配项正在处理中的项无法被清除匹配不到的项通过free释放lib/thrdqueue.cCurl_thrdq_await_done在单元测试构建UNITTESTS下提供等价于Curl_thrdpool_await_idle等待队列处理完毕lib/thrdqueue.cCurl_thrdq_set_props/Curl_thrdq_trace分别对应池的属性调整与跟踪输出lib/thrdqueue.c。生产实践线程化 DNS 解析器如何使用队列文档所述机制在 curl 中最典型也是当前仓库中唯一的使用者是多线程 DNS 解析器 lib/vdns/asyn-thrdd.c它完整演绎了队列 池的接入方式创建Curl_async_thrdd_multi_init()lib/vdns/asyn-thrdd.c为每个Curl_multi实例创建一个名为DNS的线程队列注册三个回调async_thrdd_item_free→freeasync_thrdd_item_process→ 在池线程里调用Curl_getaddrinfo_ex()/Curl_ipv4_resolve_r()执行真实解析lib/vdns/asyn-thrdd.casync_thrdd_event→ 收到CURL_THRDQ_EV_ITEM_DONE事件时调用Curl_multi_wakeup_internal(multi)唤醒 multi 事件循环在支持内部唤醒的构建中lib/vdns/asyn-thrdd.c。这一设计把解析完成从工作线程安全地传递回事件循环。工作项async_thrdd_item_create()lib/vdns/asyn-thrdd.c把一次解析请求hostname、port、transport、查询类型 A/AAAA 等封装为一个struct async_thrdd_item每个工作项还带有一个用于跟踪的描述串。发送async_thrdd_query()调用Curl_thrdq_send(..., async-timeout_ms)投递解析请求lib/vdns/asyn-thrdd.c——timeout_ms正是解析超时若项在发送队列滞留过久会被直接转入接收队列。接收与派发Curl_async_thrdd_multi_process()循环调用Curl_thrdq_recv()取回已处理项再根据工作项内的midmulti 句柄 id与resolv_id找到对应的传输与异步状态把解析结果res_A/res_AAAA挂到对应请求上并标记传输为 dirty若对应请求已不存在则直接释放该项lib/vdns/asyn-thrdd.c。清除与销毁当某个传输被取消时Curl_async_thrdd_destroy()通过Curl_thrdq_clear()按mid resolv_id精确清除队列中尚未处理的解析项lib/vdns/asyn-thrdd.cmulti 销毁时Curl_async_thrdd_multi_destroy()调用Curl_thrdq_destroy(multi-resolv_thrdq, join)整体拆除lib/vdns/asyn-thrdd.c。单元测试验证仓库在 tests/unit 下为这套机制提供了专门的单元测试是理解其行为契约的极佳样本tests/unit/unit3300.c 与 tests/unit/unit3306.c覆盖线程池相关行为tests/unit/unit3301.c完整演示了队列的生命周期——创建队列0, 2, 1即 min0 / max2 / idle1ms、发送 10 个工作项、通过thrdq_await_done等待全部处理完成、再逐个Curl_thrdq_recv取回并校验每个项的processed标志tests/unit/unit3301.c。其unit3301_event回调也验证了CURL_THRDQ_EV_ITEM_DONE事件在每次项处理完成时触发。这些测试同时印证了文档中的关键约定process不能失败、free负责资源回收、销毁时未处理项会被free掉。小结三张契约表速查线程池Curl_thrdpool维度契约属性min_threads默认 0、max_threads、idle_time_ms0 为无限等待回调take锁内返回 NULL 即空闲、process锁外不可失败、return锁内池销毁后user_data为 NULL关键接口Curl_thrdpool_create/Curl_thrdpool_signal/Curl_thrdpool_destroy(pool, join)/Curl_thrdpool_await_idle/Curl_thrdpool_set_props安全规则回调不得再调用池函数被 take 的项不得被外部继续引用return必被调用线程队列Curl_thrdq维度契约属性池属性 发送队列长度限制0 不限回调free任意线程任意时刻、process不可失败、event锁外、可选关键接口Curl_thrdq_create/Curl_thrdq_send(item, timeout_ms)/Curl_thrdq_recv/Curl_thrdq_clear/Curl_thrdq_destroy(join)/Curl_thrdq_check_started安全规则发送后放弃项引用资源由free回收process锁外执行理解这两层抽象是阅读 curl 多线程解析器乃至在其上扩展异步处理能力的起点。如需进一步了解底层线程原语互斥锁、条件变量、线程创建的封装可继续阅读 lib/curl_threads.h 与其平台实现以及队列依赖的 lib/llist.h 双向链表。【免费下载链接】curlA command line tool and library for transferring data with URL syntax, supporting DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS and WSS. libcurl offers a myriad of powerful features项目地址: https://gitcode.com/GitHub_Trending/cu/curl创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表