socket.close() 不可在多线程中直接调用,否则引发未定义行为和崩溃;应先 shutdown() 断流,再由唯一线程安全调用 close(),boost.asio 中须在 io_context 线程内用 cancel() 后 close()。

socket.close() 在多线程中直接调用会崩溃
不能在业务线程里直接调用 close() 或 closesocket(),尤其当主线程还在用 recv()、send() 或异步操作(如 boost.asio 的 async_read_some())时。系统可能仍在访问已释放的 socket 句柄,触发 WSAENOTSOCK、EBADF 或访问违规(access violation)。这不是“偶尔出错”,而是未定义行为——崩溃时机取决于调度和内核状态。
- Windows 下常见错误码:
WSAENOTSOCK(句柄无效)、WSAENOTCONN(连接已关闭但 recv 还在等) - Linux 下常见错误码:
EBADF(bad file descriptor)、EAGAIN(非阻塞模式下误判为可读) - boost.asio 场景下更危险:
socket::close()会取消所有 pending 操作,但回调仍可能在任意线程执行,若回调里再访问已析构的对象,就是典型的 use-after-free
用 shutdown() + close() 分两步断开连接
shutdown() 是安全切断数据流的起点,它不释放句柄,只通知 TCP 层停止收/发,让对方能收到 FIN 包并优雅退出。之后再由唯一持有句柄的线程调用 close() 或 closesocket()。
- 先调
shutdown(sockfd, SHUT_RDWR)(或SD_BOTHon Windows),确保对端能感知关闭 - 避免在多个线程重复调
shutdown()—— 它是幂等的,但多次调用无意义且可能干扰状态判断 - 真正释放资源(即调
close())必须限定在**一个线程内完成**,通常是负责该 socket 生命周期管理的线程(比如 accept 后创建的工作线程,或 io_context 所在线程) - 如果用了 boost.asio,优先走
socket::close()而非原始close(),因为它会自动处理 pending 操作取消和 error_code 分发
boost.asio 中如何避免 async_read 与 close 竞态
asio 的核心规则是:**所有对同一个 socket 对象的成员函数调用,必须保证线程安全**。这意味着不能一边在 io_context 线程里跑 async_read_some(),一边在业务线程里调 socket::close()。
- 正确做法:用
socket::cancel()主动取消 pending 操作,再调socket::close();两者都应在同一 io_context 线程中执行(通过post()投递) - 示例:
io_context.post([&socket] { socket.cancel(); socket.close(); }); - 不要依赖
error_code判断是否已关闭——async_read回调里收到boost::asio::error::operation_aborted表示被 cancel,收到boost::asio::error::eof或boost::asio::error::connection_reset才表示对端关闭 - 如果 socket 已 move 构造过(比如传给 lambda 捕获),确保它没被提前析构;推荐用
shared_ptr<:socket></:socket>管理生命周期
RAII + 原子标志控制线程退出路径
socket 关闭往往和线程退出强耦合。线程自己不主动关 socket,而是响应外部信号后,在退出前统一清理。
- 声明
std::atomic<bool> stop_requested{false};</bool>,所有工作循环都用while (!stop_requested.load())判断 - 主线程调
stop_requested.store(true)后,必须join()等待线程退出,不能只改标志就不管 - 线程函数退出前,按顺序做三件事:调
shutdown()→ 处理完剩余 recv/send → 最后调close();每一步都要检查返回值 - 用 RAII 封装 socket:例如自定义
scoped_socket类,在析构里调close(),但前提是确保析构发生在线程安全上下文中(比如线程函数栈展开时)
真正麻烦的从来不是“怎么发关闭指令”,而是“哪个线程、在什么时机、以什么顺序释放 socket 和配套资源”。漏掉一次 shutdown(),或让两个线程同时碰 close(),都可能让服务卡死或静默丢包。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











