Socket编程中客户端切换至STATE_ONLINE状态时出现死锁求助
死锁原因分析
- 锁保护不完整:
isState()和getSocket()直接访问未受锁保护的成员变量state和socket,导致数据竞争,可能引发线程间异常同步行为,进而触发死锁。 - 动态管理
unique_lock的风险:通过new/delete手动管理unique_lock生命周期,不仅容易引发内存泄漏,还可能因异常处理疏漏导致锁未正确释放。 - 锁顺序不一致:如果其他线程(如客户端消息处理线程)以与当前线程相反的顺序尝试锁定多个
client对象,会直接导致循环等待的死锁。 - 线程间同步依赖错误:若处理客户端的线程持有
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
相关产品推荐
相关产品推荐

