直接使用std::thread为每个连接创建线程不可行,因线程栈内存开销大、系统线程数有限制、上下文切换成本随线程数平方级增长,且阻塞式I/O导致CPU利用率低、延迟高,万级并发时易触发EAGAIN或资源耗尽。

为什么不能靠“开更多线程”解决高并发
直接用 std::thread 为每个连接起一个线程,在千级连接下就容易崩——线程栈默认几 MB,万级连接光栈内存就吃掉几十 GB;更关键的是,Linux 对可创建线程数有硬限制(/proc/sys/kernel/threads-max),且上下文切换成本随线程数平方级增长。这不是性能瓶颈,是系统资源耗尽的前兆。
- 阻塞式
recv()+ 独立线程 = 大量线程长期休眠,CPU 利用率低、响应延迟高 - 连接数 > 1000 时,
pthread_create可能直接返回errno == EAGAIN - 即使线程池复用,若池大小固定且远小于连接数,新请求会排队等待空闲线程,失去实时性
epoll + 单线程事件循环才是 Linux 高并发主线
真正扛住万级并发的核心不是多线程,而是让单个线程通过 epoll_wait() 同时监控成千上万个 socket fd 的读写就绪状态。所有 I/O 操作非阻塞,线程永不因等待数据而挂起。
- 必须把 socket 设为非阻塞模式:
fcntl(fd, F_SETFL, O_NONBLOCK),否则recv()可能意外阻塞 -
epoll_ctl()注册 fd 时,推荐用EPOLLET(边缘触发),避免重复通知;但需配合循环recv()直到返回EAGAIN - 业务逻辑不能在
epoll_wait()回调里做重计算(如 JSON 解析、DB 查询),否则阻塞整个事件循环——应投递到独立线程池处理
std::queue
- 线程池 size 建议设为
std::thread::hardware_concurrency(),而非盲目加大 - 从事件循环线程向计算线程传数据时,用
std::shared_ptr管理 buffer 生命周期,避免悬垂指针 - 计算线程结果回传给网络线程时,需通过
epoll_ctl(EPOLL_CTL_MOD)唤醒事件循环,或用 eventfd 通知
std::mutex 不是万能解药,死锁和伪共享才是真坑
高频网络服务里,std::mutex 锁临界区一旦过长(>100μs),就会成为吞吐瓶颈;更危险的是锁顺序不一致导致的死锁——比如线程 A 先锁 conn_mutex 再锁 cache_mutex,线程 B 反过来,瞬间卡死。
- 优先用
std::atomic处理计数器、状态标志等简单变量(如连接数统计、健康检查开关) - 多个 mutex 必须按固定地址顺序加锁:用
std::lock(mtx1, mtx2)替代分别lock_guard,它内部用死锁避免算法 - 避免在锁内调用可能阻塞的函数(如
malloc、getaddrinfo),它们可能间接持有 glibc 内部锁
实际部署时,最易被忽略的是 **连接生命周期与内存释放的耦合**:一个连接关闭后,其关联的 buffer、定时器、业务上下文必须全部清理干净,且不能依赖析构函数——因为对象可能还在计算线程里被引用。手动管理比 RAII 更可靠,也更难出错。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











