如何在线程内部安全停止线程?Winsock监听线程触发abort问题
问题分析
你遇到的abort()是因为线程自身调用了join()——监听线程在执行回调on_client_connected时触发了Listener::stop(),而stop()里调用了m_listen_thread.join(),这属于线程等待自身结束,是C++标准明确禁止的未定义行为,触发abort是合理结果。
解决方案
下面提供两种可行实现方式,都能满足“接受一个客户端后立即停止监听,最多一个客户端连接”的需求:
方式一:监听线程处理完首个客户端后主动退出
这种方式无需在回调里调用stop(),让监听线程处理完第一个客户端后直接终止循环、自然退出,简单直接:
struct Listener { using accept_callback = void(*)(Client &&); ~Listener() { stop(); // 析构时确保资源清理 WSACleanup(); } Listener(const std::basic_string<TCHAR> &a_ip, std::uint16_t a_port, accept_callback a_callback) { m_on_connection_accepted = a_callback; WSADATA sockets_data{}; WSAStartup(MAKEWORD(2, 2), &sockets_data); m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); sockaddr_in service { .sin_family = AF_INET, .sin_port = htons(a_port) }; InetPton(service.sin_family, a_ip.data(), &service.sin_addr); bind(m_socket, reinterpret_cast<SOCKADDR *>(&service), sizeof(service)); listen(m_socket, SOMAXCONN); // 修复捕获方式:用[this]替代[&],避免构造函数结束后悬空引用 m_listen_thread = std::jthread([this] { while (m_socket != INVALID_SOCKET) { SOCKET client = accept(m_socket, NULL, NULL); if (client == INVALID_SOCKET) break; // 处理客户端连接 m_on_connection_accepted(std::move(Client{ client })); // 处理完首个客户端后,主动退出循环,停止监听 break; } // 清理监听socket if (m_socket != INVALID_SOCKET) { closesocket(m_socket); m_socket = INVALID_SOCKET; } }); } void stop() { if (m_socket != INVALID_SOCKET) { closesocket(m_socket); m_socket = INVALID_SOCKET; } if (m_listen_thread.joinable()) { // 只有非监听线程调用时才join,避免自join if (std::this_thread::get_id() != m_listen_thread.get_id()) { m_listen_thread.join(); m_listen_thread = {}; } } } private: accept_callback m_on_connection_accepted{ nullptr }; SOCKET m_socket{ INVALID_SOCKET }; std::jthread m_listen_thread{}; };
对应的回调函数可简化,无需调用stop():
void on_client_connected(sockets::Client &&a_new_client) { // … 处理客户端逻辑 … // 无需调用m_listener.stop(),监听线程已主动退出 }
方式二:允许回调调用stop(),避免线程自join
如果必须在回调里触发停止逻辑,需要修改stop()函数,判断当前调用线程是否为监听线程,仅当其他线程调用时才执行join():
struct Listener { using accept_callback = void(*)(Client &&); ~Listener() { stop(); WSACleanup(); } Listener(const std::basic_string<TCHAR> &a_ip, std::uint16_t a_port, accept_callback a_callback) { m_on_connection_accepted = a_callback; WSADATA sockets_data{}; WSAStartup(MAKEWORD(2, 2), &sockets_data); m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); sockaddr_in service { .sin_family = AF_INET, .sin_port = htons(a_port) }; InetPton(service.sin_family, a_ip.data(), &service.sin_addr); bind(m_socket, reinterpret_cast<SOCKADDR *>(&service), sizeof(service)); listen(m_socket, SOMAXCONN); m_listen_thread = std::jthread([this] { while (m_socket != INVALID_SOCKET) { SOCKET client = accept(m_socket, NULL, NULL); if (client == INVALID_SOCKET) break; m_on_connection_accepted(std::move(Client{ client })); } if (m_socket != INVALID_SOCKET) { closesocket(m_socket); m_socket = INVALID_SOCKET; } }); // 保存监听线程ID,用于后续判断 m_listen_thread_id = m_listen_thread.get_id(); } void stop() { // 先关闭socket,让阻塞的accept()立即返回错误 if (m_socket != INVALID_SOCKET) { closesocket(m_socket); m_socket = INVALID_SOCKET; } if (m_listen_thread.joinable()) { // 仅当调用线程不是监听线程时,才执行join if (std::this_thread::get_id() != m_listen_thread_id) { m_listen_thread.join(); m_listen_thread = {}; } // 如果是监听线程自身调用stop,跳过join,让线程自然结束 } } private: accept_callback m_on_connection_accepted{ nullptr }; SOCKET m_socket{ INVALID_SOCKET }; std::jthread m_listen_thread{}; std::thread::id m_listen_thread_id; // 新增:保存监听线程ID };
此时回调函数可保留调用stop()的逻辑,不会再触发abort:
void on_client_connected(sockets::Client &&a_new_client) { // … 处理客户端逻辑 … m_listener.stop(); }
额外注意事项
- 线程捕获方式:原代码用
[&]捕获构造函数局部变量,存在悬空引用风险,必须改为[this]捕获成员变量。 - 线程安全:若需在多线程环境下访问
m_socket,建议将其声明为std::atomic<SOCKET>,避免数据竞争。
内容的提问来源于stack exchange,提问作者PaperBirdMaster
相关产品推荐
相关产品推荐

