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

Socket编程中客户端切换至STATE_ONLINE状态时出现死锁求助

死锁原因分析

  1. 锁保护不完整:isState()和getSocket()直接访问未受锁保护的成员变量state和socket,导致数据竞争,可能引发线程间异常同步行为,进而触发死锁。
  2. 动态管理unique_lock的风险:通过new/delete手动管理unique_lock生命周期,不仅容易引发内存泄漏,还可能因异常处理疏漏导致锁未正确释放。
  3. 锁顺序不一致:如果其他线程(如客户端消息处理线程)以与当前线程相反的顺序尝试锁定多个client对象,会直接导致循环等待的死锁。
  4. 线程间同步依赖错误:若处理客户端的线程持有client锁的同时,等待当前线程的select操作结果(如未使用条件变量,而是循环检查共享fd_set),会形成线程间互相等待,引发死锁。

修复方案

1. 完善线程安全的成员访问

修改client类,确保所有成员变量的访问都在锁的保护下:

class client{
    std::mutex _lock;
    std::condition_variable cv;
    SOCKET socket;
    STATE state;
    bool has_pending_data = false;
public:
    int id;
    using mutex_type = std::mutex;
    using u_lock = std::unique_lock<mutex_type>;

    // 返回栈上的unique_lock,避免动态分配
    u_lock lock_guard() {
        return u_lock(_lock);
    }

    u_lock try_lock_guard() {
        std::cout << "trying locking" << std::endl;
        return u_lock(_lock, std::try_to_lock);
    }

    // 带锁的状态检查,要求调用者已持有当前对象的锁
    bool isState(STATE target_state, const u_lock& lock) {
        assert(lock.mutex() == &_lock && lock.owns_lock());
        return ((this->state & target_state) != 0);
    }

    // 带锁的socket获取
    SOCKET getSocket(const u_lock& lock) {
        assert(lock.mutex() == &_lock && lock.owns_lock());
        return socket;
    }

    // 线程安全的状态修改函数
    void setState(STATE new_state) {
        std::lock_guard<mutex_type> lock(_lock);
        this->state = new_state;
    }

    // 用于线程同步的条件变量接口
    void notifyPendingData() {
        std::lock_guard<mutex_type> lock(_lock);
        has_pending_data = true;
        cv.notify_one();
    }

    void waitForPendingData(u_lock& lock) {
        cv.wait(lock, [this]{ return has_pending_data; });
        has_pending_data = false;
    }
};

2. 优化主循环的锁使用

改用栈上的unique_lock自动管理生命周期,确保锁的正确释放:

// 无限循环 
while (true) {
    FD_ZERO(&readfds);
    int current_max_sd = 0;

    for (int i = 0; i < DEFAULT_CLIENT_NUMBER_THREAD; i++) {
        auto lockGuard = clients[i]->try_lock_guard();
        std::cout << "fd_set creation try " << i << std::endl;

        if (lockGuard.owns_lock()) {
            std::cout << "fd_set creation owns "<< i << std::endl;
            try {
                if (clients[i]->isState(STATE_ONLINE, lockGuard)) {
                    SOCKET sd = clients[i]->getSocket(lockGuard);
                    if (sd > 0) {
                        std::cout << "adding " << i << " to fd_set" << std::endl;
                        FD_SET(sd, &readfds);
                        if (sd > current_max_sd) {
                            current_max_sd = sd;
                        }
                    }
                }
            }
            catch (...) {
                std::cout << "fd_set exception" << std::endl;
            }
        }
        // lockGuard自动析构,无需手动delete
    }

    // 正确计算select参数:最大socket描述符 +1
    int activity = select(current_max_sd > 0 ? current_max_sd + 1 : 0, &readfds, NULL, NULL, NULL);

    // 处理可读socket:通知对应客户端线程
    for (int i = 0; i < DEFAULT_CLIENT_NUMBER_THREAD; i++) {
        auto lockGuard = clients[i]->try_lock_guard();
        if (lockGuard.owns_lock() && clients[i]->isState(STATE_ONLINE, lockGuard)) {
            SOCKET sd = clients[i]->getSocket(lockGuard);
            if (FD_ISSET(sd, &readfds)) {
                clients[i]->notifyPendingData();
            }
        }
    }
}

3. 统一锁的获取顺序

确保所有线程在需要锁定多个client对象时,都遵循相同的顺序(比如按client.id从小到大),避免循环等待引发的死锁。

4. 修复客户端线程同步逻辑

使用条件变量替代循环等待,避免线程持锁阻塞:

// 客户端处理线程逻辑
void handleClient(client* c) {
    auto lock = c->lock_guard();
    while (true) {
        c->waitForPendingData(lock);
        // 接收并处理socket数据
        char buf[1024];
        int bytes_recv = recv(c->getSocket(lock), buf, sizeof(buf), 0);
        if (bytes_recv <= 0) {
            c->setState(STATE_OFFLINE);
            break;
        }
        // 处理数据逻辑
    }
}

内容的提问来源于stack exchange,提问作者l9sik

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 18:15:35