You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在线程内部安全停止线程?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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 19:18:12