直接使用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 查询),否则阻塞整个事件循环——应投递到独立线程池处理
int epoll_fd = epoll_create1(0);
struct epoll_event ev;
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = client_fd;
epoll_ctl(epoll_fd, EP_ADD, client_fd, &ev);
while (true) {
int n = epoll_wait(epoll_fd, events, 1024, -1); // 单线程等所有 fd
for (int i = 0; i
<h3>多线程只用于 CPU 密集型任务,且要隔离 I/O</h3>
<p>网络层(socket 收发、协议解析)必须在事件循环线程完成;只有纯计算类工作(如加密解密、图像处理、复杂业务规则校验)才扔给线程池。二者之间用无锁队列或 <code>std::queue</code> + <code>std::mutex</code> 传递任务指针,严禁跨线程共享 socket fd 或 buffer。</p>
- 线程池 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 内部锁
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











