libuv事件循环需手动启动,uv_run()不会自动返回;所有handle、request、buffer生命周期须自行管理,避免野指针、泄漏或崩溃。

libuv 事件循环必须手动启动,uv_run() 不会自动返回
很多人写完 uv_tcp_init()、uv_fs_open() 就以为 IO 会立刻跑起来,结果程序直接退出——因为没调 uv_run(),或者调了但传了错误的 mode。libuv 的事件循环是显式驱动的,不启动就等于没通电。
实操建议:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
-
uv_run(loop, UV_RUN_DEFAULT)是最常用模式,它会阻塞直到没有活跃 handle 或 request,适合主循环 - 如果只是想处理一次就返回(比如做异步文件读取后继续其他逻辑),用
UV_RUN_ONCE,但要注意:它只轮询一次,哪怕还有 pending request 也会退出 - 别在
uv_run()前释放uv_loop_t*,也不要在回调里调uv_loop_close()后再进uv_run(),会 crash - 网络监听要先
uv_tcp_bind()再uv_listen(),漏掉 bind 会导致UV_EADDRNOTAVAIL
网络 IO 中,uv_tcp_t 和 uv_connect_t 的生命周期必须自己管
libuv 不帮你 malloc/free 连接相关的结构体,所有 uv_handle_t 和 uv_req_t 都是栈或堆上由你分配的裸内存。一旦回调触发,handle 可能已被关掉,但 request 结构体还在——这时候访问已释放的 uv_connect_t* 就是野指针。
实操建议:
- 把
uv_connect_t放在堆上(比如 new 出来),并在connect_cb最后delete它;不要放在栈上并传地址进去 -
uv_tcp_t句柄关闭后,不能立刻重用,得等close_cb触发后再 memset 或重新 init - 服务端 accept 回调里,每次都要 new 一个新的
uv_tcp_t来代表客户端连接,别复用 listener 的句柄 - 忘记调
uv_close()会导致 handle 泄漏,uv_run()永远不会退出
文件 IO 用 uv_fs_open() + uv_fs_read() 时,buffer 必须保持有效直到 read_cb 返回
libuv 的文件操作是真正的异步(底层用线程池模拟),uv_fs_read() 提交后立即返回,但内核可能几毫秒后才真正把数据拷到你给的 buffer 里。如果 buffer 是栈变量或提前 delete 的堆内存,read_cb 里读到的就是垃圾。
实操建议:
- 用
uv_buf_t包装 buffer 时,确保其指向的内存生命周期 ≥ 整个异步读过程 - 常见做法:把 buffer 和
uv_fs_trequest 一起 new 出来,绑在同一个结构体里,在read_cb末尾统一 delete -
uv_fs_open()的 flag 要明确,比如UV_FS_O_RDONLY,别直接传数字 0;Windows 下不支持O_NONBLOCK,libuv 已屏蔽 - 同步文件操作(如
uv_fs_read_sync)会阻塞线程,仅用于初始化或调试,别在 event loop 线程里用
多线程下不能跨线程操作同一个 uv_loop_t,但可以安全地从其他线程向 loop 投递任务
libuv 的 loop 不是线程安全的,不能在 worker 线程里直接调 uv_tcp_write() 或 uv_fs_write()。但你可以用 uv_async_send() 或 uv_queue_work() 把任务“转交”给 loop 所在线程执行。
实操建议:
- 主线程创建 loop,worker 线程通过
uv_async_t发信号,async_cb 在 loop 线程里执行真正的 IO 操作 -
uv_queue_work()更适合耗时计算或阻塞系统调用,它会把 work_cb 交给线程池,done_cb 回到 loop 线程——注意 done_cb 里才能安全操作 handle - 别在 worker 线程里调
uv_run(),loop 必须且只能由一个线程运行 - 跨线程访问自定义数据时,仍需加锁(libuv 不管你的业务数据),
uv_async_t本身不带 payload,要自己用成员变量或 closure 传参
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










