【Linux】poll 多路转接:select 的改良版,以及它留下的遗憾

【Linux】poll 多路转接:select 的改良版,以及它留下的遗憾

文章目录

poll 多路转接:select 的改良版,以及它留下的遗憾

💬 开篇:上一篇我们把 select 搞清楚了,也知道了它的四个缺点。poll 就是针对其中最让人头疼的问题——fd 数量上限——做出的改进。它用一个更合理的数据结构替代了位图,让接口更清晰,也去掉了 1024 的限制。但 poll 并没有从根本上解决 select 的所有问题,本质的"每次全量拷贝 + O(n) 遍历"依然存在。

这篇文章我们深度解析 poll 的接口设计,讲清楚它相比 select 的进步在哪里,局限在哪里,最后用 poll 实现一个完整的服务器。理解了 poll,后面对 epoll 的学习会更有感觉——因为你能看清楚每一步改进背后的动机。

👍 点赞、收藏与分享:select → poll → epoll 是 Linux IO 多路复用的演进主线,poll 是中间承上启下的一环。

🚀 循序渐进:poll 接口 → pollfd 结构体 → 执行过程 → 优缺点 → 完整服务器实现。

一、select 的痛点回顾

1.1 select 的问题在哪里?

学 poll 之前,先把 select 的缺陷再明确一下,因为 poll 的设计就是奔着解决这些问题去的:

问题 1:fd 数量上限 1024

// select 用位图,fd_set 大小固定// FD_SETSIZE = 1024(多数系统)// 超过 1024 个连接直接没辙

问题 2:接口设计不友好

// select 用三个独立的位图,输入输出混在一起// 每次调用前必须手动重建集合// 读就绪、写就绪、异常三个 fd_set 分开管理,麻烦 fd_set readfds, writefds, exceptfds;// 三个集合FD_ZERO(&readfds);FD_SET(fd,&readfds);// select 返回后 readfds 被修改,必须重建...

问题 3 & 4:每次拷贝 + O(n) 遍历(核心性能问题,poll 没解决)

poll 主要解决了问题 1 和 2,问题 3 和 4 要等 epoll 来解决。


二、poll 函数接口详解

2.1 函数原型

#include<poll.h>intpoll(structpollfd*fds,nfds_t nfds,int timeout);

和 select 相比,参数少了一个(不需要分开传三个 fd_set),接口更简洁。


2.2 核心数据结构:pollfd

poll 的关键在于 pollfd 结构体:

structpollfd{int fd;/* 要监控的文件描述符 */short events;/* 关注的事件(输入参数)*/short revents;/* 实际发生的事件(输出参数)*/};

eventsrevents 的取值:

宏名含义
POLLIN0x0001数据可读(包括普通数据和优先数据)
POLLPRI0x0002高优先级数据可读(带外数据)
POLLOUT0x0004数据可写
POLLERR0x0008发生错误(仅 revents 有效)
POLLHUP0x0010挂断(仅 revents 有效)
POLLNVAL0x0020非法的 fd(仅 revents 有效)
关键设计eventsrevents 分开!events:你设置,告诉内核你关注什么(输入)revents:内核设置,告诉你实际发生了什么(输出)poll 返回后,events不会被修改,只有 revents 被更新

这解决了 select 每次要重建集合的问题:你只需要检查 revents,而 events 始终保持你的设置,下次调用时不需要重新赋值(但 revents 需要清零)。

2.3 参数详解

参数 fdspollfd 结构体数组的首地址,每个元素对应一个要监控的 fd。

参数 nfdsfds 数组的长度,即监控的 fd 数量。

参数 timeout:超时时间(毫秒)。

timeout 值行为
-1无限等待(永远阻塞)
0立即返回,只检查当前状态
> 0等待最多 timeout 毫秒

2.4 返回值

int ret =poll(fds, nfds, timeout);// ret > 0:就绪的 fd 数量// ret == 0:超时// ret < 0:出错,查 errno

三、poll vs select:对比分析

3.1 数据结构对比

select 的位图方式: fd_set:[bit0, bit1, bit2, ..., bit1023] 最多 1024 个 fd poll 的 pollfd 数组: pollfd[0]:{fd=3, events=POLLIN, revents=0} pollfd[1]:{fd=5, events=POLLIN|POLLOUT, revents=0} pollfd[2]:{fd=7, events=POLLIN, revents=0}... 数组大小由用户决定,理论上无上限 

poll 就像把 select 的位图升级成了一个更富有表达力的结构体数组。每个 fd 的信息自成一体,不需要在三个独立的位图之间查找。


3.2 使用方式对比

select 的使用(每次循环都很麻烦)

// select:每次循环必须重建三个 fd_setfor(;;){ fd_set readfds, writefds;FD_ZERO(&readfds);FD_ZERO(&writefds);for(int i =0; i < n; i++)FD_SET(fds[i],&readfds);select(max_fd +1,&readfds,&writefds,NULL,&timeout);// 检查结果for(int i =0; i < n; i++){if(FD_ISSET(fds[i],&readfds)){/* 处理 */}}}

poll 的使用(清晰多了)

// poll:只需要清零 revents,events 保持不变structpollfd pfds[MAX_FDS];// 初始化一次就好: pfds[0]={fd1, POLLIN,0}; pfds[1]={fd2, POLLIN | POLLOUT,0};for(;;){// 清零所有 revents(可选,但推荐)for(int i =0; i < n; i++) pfds[i].revents =0;poll(pfds, n,-1);// 检查结果for(int i =0; i < n; i++){if(pfds[i].revents & POLLIN){/* 处理读 */}if(pfds[i].revents & POLLOUT){/* 处理写 */}}}

3.3 优缺点总结

poll 相比 select 的优点:

改进项selectpoll
fd 数量限制1024(固定)无上限(数组大小可动态扩展)
接口设计三个位图,输入输出混用pollfd 结构体,events/revents 分离
重建集合每次必须重建events 保持不变,只需清零 revents
事件表达三个集合(读/写/异常)单结构体内用 events/revents 标志

poll 与 select 共同的缺点(核心性能问题):

问题selectpoll
用户态到内核态拷贝每次拷贝整个 fd_set每次拷贝整个 pollfd 数组
内核查找就绪 fd遍历所有 fd,O(n)遍历所有 pollfd,O(n)

结论:poll 是 select 的改良版,解决了接口设计问题和数量限制,但没有从根本上解决性能问题。当连接数成千上万时,poll 和 select 都会因为 O(n) 遍历而性能下降。


四、poll 执行过程图解

4.1 一次 poll 调用的完整流程

用户态 内核态 ||| 初始化 pollfd 数组 || pfds[0]={3, POLLIN, 0}|| pfds[1]={5, POLLIN, 0}|| pfds[2]={7, POLLIN, 0}|||| poll(pfds, 3, -1)||--- 拷贝 3 个 pollfd 到内核 --->||||(程序阻塞在 poll)| 轮询每个 fd 的状态 ||fd=3:未就绪 ||fd=5:就绪!(有数据)||fd=7:未就绪 |||| 设置就绪的 revents: || pfds[1].revents = POLLIN |||<--- 返回 1(1 个 fd 就绪)-----|||| 检查 pfds[i].revents || pfds[1].revents & POLLIN → 处理 fd=5

五、最简单的 poll 示例

5.1 使用 poll 监控标准输入

#include<stdio.h>#include<unistd.h>#include<poll.h>intmain(){// 定义一个 pollfd,监控标准输入(fd=0)structpollfd poll_fd; poll_fd.fd =0; poll_fd.events = POLLIN;// 关注可读事件 poll_fd.revents =0;for(;;){// 超时 1000ms(1 秒)int ret =poll(&poll_fd,1,1000);if(ret <0){perror("poll");continue;}if(ret ==0){printf("poll timeout(1 秒内无输入)\n");continue;}// 检查是否有读事件就绪if(poll_fd.revents & POLLIN){char buf[1024]={0};read(0, buf,sizeof(buf)-1);printf("stdin: %s", buf);}// 清零 revents,为下一轮做准备(poll 不会自动清零) poll_fd.revents =0;}return0;}

运行效果:

  • 1 秒内没有输入 → 打印 “poll timeout”
  • 有输入 → 打印输入内容

六、完整的 PollServer 实现

6.1 设计思路

用 poll 实现服务器,核心思路和 select 版本一样:

  1. 维护一个 pollfd 数组,代替 select 的 fd_set
  2. 用 -1 标记"这个槽位空闲"(因为 poll 数组可能有空洞)
  3. 新连接来了,找一个空闲槽位放入;连接断开,把那个槽位标记为 -1

教学版使用阻塞 socket + poll,适合短消息;生产环境通常配合 非阻塞 fd + 发送缓冲,避免慢连接在 send() 上阻塞整个事件循环

pollfd 数组的管理: [0]: {listensock, POLLIN, 0} ← 监听 socket,一直在 [1]: {fd=4, POLLIN, 0} ← 客户端 A [2]: {-1, 0, 0} ← 空闲槽位 [3]: {fd=6, POLLIN, 0} ← 客户端 B [4]: {-1, 0, 0} ← 空闲槽位 

6.2 完整代码

// poll_server.hpp#pragmaonce#include<iostream>#include<string>#include<memory>#include<poll.h>#include<sys/socket.h>#include<netinet/in.h>#include<arpa/inet.h>#include<unistd.h>#include<cerrno>#include<cstring>conststaticint g_default_port =8888;conststaticint g_backlog =8;conststaticint g_max_fds =1024;// pollfd 数组的初始大小/** * 简单的 TCP Socket 封装(复用自上一篇) */classTcpSocket{public:TcpSocket(int fd =-1):fd_(fd){}intGetFd()const{return fd_;}boolBuild(int port){ fd_ =socket(AF_INET, SOCK_STREAM,0);if(fd_ <0)returnfalse;int opt =1;setsockopt(fd_, SOL_SOCKET, SO_REUSEADDR,&opt,sizeof(opt));structsockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port =htons(port); addr.sin_addr.s_addr = INADDR_ANY;if(bind(fd_,(structsockaddr*)&addr,sizeof(addr))<0)returnfalse;if(listen(fd_, g_backlog)<0)returnfalse;returntrue;}intAcceptConnection(std::string* ip =nullptr,uint16_t* port =nullptr){structsockaddr_in peer; socklen_t len =sizeof(peer);int sock =accept(fd_,(structsockaddr*)&peer,&len);if(sock <0)return-1;if(ip)*ip =inet_ntoa(peer.sin_addr);if(port)*port =ntohs(peer.sin_port);return sock;}intGetSockFd()const{return fd_;}private:int fd_;};/** * 基于 poll 的 TCP 服务器 */classPollServer{public:PollServer(int port = g_default_port):_port(port),_listen_sock(std::make_unique<TcpSocket>()),_is_running(false),_num(g_max_fds){}voidInitServer(){// 初始化监听 socketif(!_listen_sock->Build(_port)){perror("build listen socket failed");exit(1);}printf("[PollServer] 服务器初始化完成,监听端口 %d\n", _port);// 初始化 pollfd 数组 _rfds =newstructpollfd[_num];for(int i =0; i < _num; i++){ _rfds[i].fd =-1;// -1 表示空闲槽位 _rfds[i].events =0; _rfds[i].revents =0;}// 把监听 socket 放入数组第 0 号位 _rfds[0].fd = _listen_sock->GetSockFd(); _rfds[0].events = POLLIN;}voidLoop(){ _is_running =true;while(_is_running){PrintDebug();int timeout =-1;// 永久阻塞int n =poll(_rfds, _num, timeout);switch(n){case0:printf("[PollServer] poll 超时\n");break;case-1:perror("poll error");break;default:// 有 n 个 fd 就绪HandleEvent(n);break;}} _is_running =false;}voidStop(){ _is_running =false;}~PollServer(){delete[] _rfds;}private:/** * 处理就绪事件 */voidHandleEvent(int ready_count){for(int i =0; i < _num; i++){if(_rfds[i].fd ==-1)continue;// 跳过空闲槽位int fd = _rfds[i].fd;short revents = _rfds[i].revents;// 只处理读就绪事件if(!(revents & POLLIN))continue;if(fd == _listen_sock->GetSockFd()){// 监听 socket 就绪:有新连接HandleNewConnection();}else{// 普通 socket 就绪:有数据可读HandleData(i, fd);}// 清零 revents(poll 不会自动清零) _rfds[i].revents =0;}}/** * 处理新连接 */voidHandleNewConnection(){ std::string client_ip;uint16_t client_port;int sock = _listen_sock->AcceptConnection(&client_ip,&client_port);if(sock ==-1){perror("accept error");return;}printf("[PollServer] 新连接:%s:%d, fd=%d\n", client_ip.c_str(), client_port, sock);// 在 pollfd 数组中找一个空闲槽位int pos =FindEmptySlot();if(pos ==-1){// 数组满了,可以扩容或拒绝printf("[PollServer] 服务器已满,拒绝连接 fd=%d\n", sock);close(sock);return;}// 将新连接加入 pollfd 数组 _rfds[pos].fd = sock; _rfds[pos].events = POLLIN; _rfds[pos].revents =0;printf("[PollServer] fd=%d 加入监控,位置 pos=%d\n", sock, pos);}/** * 处理普通连接的数据 */voidHandleData(int pos,int fd){char buffer[1024]={0}; ssize_t n =recv(fd, buffer,sizeof(buffer)-1,0);if(n >0){// 正常数据 buffer[n]='\0';printf("[PollServer] fd=%d 收到:%s\n", fd, buffer);// 简单的回显服务:原样返回 std::string response = std::string("服务器收到:")+ buffer;send(fd, response.c_str(), response.size(),0);}elseif(n ==0){// 客户端正常关闭printf("[PollServer] fd=%d 正常断开\n", fd);CloseConnection(pos);}else{// 出错if(errno != EINTR){perror("recv error");printf("[PollServer] fd=%d 出错,关闭\n", fd);CloseConnection(pos);}}}/** * 关闭连接,清理 pollfd 槽位 */voidCloseConnection(int pos){close(_rfds[pos].fd); _rfds[pos].fd =-1; _rfds[pos].events =0; _rfds[pos].revents =0;}/** * 在数组中找第一个空闲槽位(fd == -1) */intFindEmptySlot(){for(int i =1; i < _num; i++){// 从 1 开始,0 是 listen_sockif(_rfds[i].fd ==-1){return i;}}return-1;// 没有空闲槽位}/** * 打印当前监控的 fd 列表(调试用) */voidPrintDebug(){printf("[PollServer] 当前监控的 fd:");for(int i =0; i < _num; i++){if(_rfds[i].fd !=-1){printf("%d ", _rfds[i].fd);}}printf("\n");}private:int _port; std::unique_ptr<TcpSocket> _listen_sock;bool _is_running;structpollfd* _rfds;// pollfd 数组int _num;// 数组大小};

主函数:

// poll_main.cc#include"poll_server.hpp"intmain(int argc,char* argv[]){int port =(argc >1)?atoi(argv[1]): g_default_port; PollServer server(port); server.InitServer(); server.Loop();return0;}

编译运行:

# 编译 g++ -std=c++14 poll_main.cc -o poll_server # 运行(监听 8888 端口) ./poll_server 8888# 另一个终端测试nc127.0.0.1 8888 hello world 

6.3 关键实现细节解析

1. 为什么用 -1 标记空闲槽位?

poll 传入的是一个数组,内核会遍历 [0, nfds) 的每个元素。如果一个槽位不再使用但没有清理,内核会继续处理它,可能会产生意外行为。

fd = -1 标记空闲,poll 会自动忽略 fd 为负数的 pollfd(POSIX 标准保证),这是一个优雅的处理方式。

// poll 的行为:fd < 0 的元素会被忽略,revents 保持 0// 所以可以安全地在数组中留下 fd = -1 的元素
2. events 不会被修改,但 revents 不会自动清零
// poll 返回后:// events:不变,始终是你设置的关注事件// revents:被内核设置为实际发生的事件// 需要注意:revents 在下一次 poll 前应该清零// 否则上次的结果会干扰判断 _rfds[i].revents =0;// 处理完事件后清零
3. 扩容逻辑(生产环境应考虑)
// 当数组满了时,可以扩容voidExpand(){int new_num = _num *2;structpollfd* new_fds =newstructpollfd[new_num];// 拷贝旧数组内容memcpy(new_fds, _rfds, _num *sizeof(structpollfd));// 初始化新增部分for(int i = _num; i < new_num; i++){ new_fds[i].fd =-1; new_fds[i].events =0; new_fds[i].revents =0;}delete[] _rfds; _rfds = new_fds; _num = new_num;printf("[PollServer] 扩容至 %d 个槽位\n", _num);}

七、select vs poll vs epoll 完整对比

7.1 三者对比总结(重要,面试必背)

比较项selectpollepoll
fd 数量限制1024(FD_SETSIZE)无限制无限制
数据结构三个位图(fd_set)pollfd 数组红黑树 + 就绪队列
用户到内核拷贝每次全量拷贝每次全量拷贝只在 ctl 时拷贝
查找就绪 fd遍历所有,O(n)遍历所有,O(n)回调机制,O(k)
集合重建每次必须重建events 保留,revents 清零内核维护,无需重建
工作模式LT 模式LT 模式LT + ET 模式
跨平台所有平台支持类 Unix 平台支持Linux 专属
适用场景连接数少(<100)连接数中等高并发(万级以上)

7.2 性能对比直觉

假设服务器有 10000 个连接,每次只有 10 个有数据: select/poll 的工作: - 每次拷贝 10000 个 fd 信息到内核 - 内核遍历 10000 个 fd,找到 10 个就绪的 - 再拷贝回用户态 - O(10000) 的工作量 epoll 的工作: - 内核维护红黑树,新 fd 只需注册一次 - 有数据时通过回调直接加入就绪队列 - epoll_wait 只返回 10 个就绪的 fd - epoll_wait 返回就绪列表,遍历成本与就绪数 k 相关(O(k)),避免每次全量扫描 n 个 fd。 连接数越多,差距越大 

八、poll 的使用场景与选择建议

8.1 什么时候选 poll 而不是 select?

  • 需要监控超过 1024 个 fd(虽然现在这种场景更应该用 epoll)
  • 代码已经用了 select,且 fd 数量接近上限,需要简单升级
  • 目标平台不支持 epoll(非 Linux 系统)

8.2 什么时候应该直接用 epoll 而不是 poll?

  • 连接数超过几百个
  • 需要高并发性能
  • 在 Linux 系统上开发
决策树: 要监控多个 fd? → 连接数少(<100)且需要跨平台? → 用 select → 不需要跨平台,Linux 系统? → 直接用 epoll(跳过 poll) → 需要跨平台,连接数中等? → 用 poll 

九、常见问题解答

9.1 poll 能同时监控读和写吗?

可以,在 events 中同时设置 POLLIN | POLLOUT

pfds[i].events = POLLIN | POLLOUT;// 同时关注读和写// poll 返回后if(pfds[i].revents & POLLIN){/* 有数据可读 */}if(pfds[i].revents & POLLOUT){/* 发送缓冲区有空间 */}
实践建议:不要一直开启 POLLOUT 监控。发送缓冲区通常都有空间,POLLOUT 几乎总是就绪,这样 poll 会不停返回,浪费 CPU。只在发送缓冲区满了、数据没发完的时候才开启 POLLOUT 监控,发完了再关掉。

9.2 poll 超时精度如何?

poll 的 timeout 参数单位是毫秒,精度受系统时钟分辨率影响,通常精度在 10ms 级别。如果需要更高精度(微秒级),需要使用 epoll_wait 或者其他高精度定时器。

9.3 POLLHUP 和 POLLERR 需要手动监控吗?

不需要。POLLHUP(挂断)和 POLLERR(错误)不需要在 events 中设置,内核会自动revents 中设置它们,即使你的 events 没有包含这两个标志。

// 不需要:pfds[i].events = POLLIN | POLLERR | POLLHUP;// 只需要: pfds[i].events = POLLIN;// poll 返回后,POLLERR 和 POLLHUP 也可能被设置在 revents 中if(pfds[i].revents & POLLERR){/* 处理错误 */}if(pfds[i].revents & POLLHUP){/* 对端关闭 */}

十、总结

10.1 核心要点

#要点关键点
1pollfd 结构体fd + events(输入)+ revents(输出),设计比 select 清晰
2无数量限制数组大小由用户决定,可动态扩容
3-1 标记空闲poll 自动忽略 fd < 0 的条目
4revents 需手动清零poll 不会自动清零,每次处理后需手动清零
5仍是 O(n)全量拷贝 + 全量遍历的问题未解决

10.2 记忆技巧

poll =select 的升级版: select 的位图 → poll 的 pollfd 结构体 1024 上限 → 无上限(数组大小由你定) 三个集合混乱 → 一个结构体,events/revents 分离 但 poll 没解决的: 每次全量拷贝到内核 → epoll 来解决 O(n) 遍历找就绪 fd → epoll 来解决 

💬 总结:poll 是 select 的进化版,核心改进是用 pollfd 结构体替代位图,消除了 fd 数量上限,让接口更清晰。但 O(n) 遍历和每次全量拷贝的性能问题依然存在。下一篇,我们终于要进入重头戏——epoll,它用红黑树 + 就绪队列 + 回调机制彻底解决了这两个问题,并引入了 LT/ET 两种工作模式。epoll 是 Linux 高性能服务器的基石,也是面试最高频的考点,务必吃透。

👍 点赞、收藏与分享:select → poll → epoll 的演进脉络是面试中展示系统理解深度的好机会。下一篇 epoll 更精彩,别走远!🚀

Read more

终极STL转STEP指南:快速实现3D格式高效转换

终极STL转STEP指南:快速实现3D格式高效转换 【免费下载链接】stltostpConvert stl files to STEP brep files 项目地址: https://gitcode.com/gh_mirrors/st/stltostp 你是否经常遇到这样的困扰:好不容易完成的3D打印模型,想要导入到CAD软件中进行进一步设计,却发现STL格式无法被识别?别担心,这正是你需要STL转STEP转换的原因所在。在现代三维设计和制造领域,STL转STEP已经成为连接3D打印与传统工程设计的必备技能。 为什么你需要STL转STEP转换 STL格式虽然适合3D打印,但在工程设计领域却存在诸多限制。当你需要将模型导入到专业CAD软件(如SolidWorks、CATIA等)进行数控加工或装配分析时,STEP格式才是真正的"通行证"。通过STL转STEP,你可以: * 在不同CAD软件之间无缝交换3D模型数据 * 为数控加工设备提供标准化的输入格式 * 保持模型的几何精度和完整性 stltostp:你的专属格式转换利器 stltostp是一款专门为你设计的ST

By Ne0inhk
【C++篇】面向对象编程的三大特性:深入解析继承机制

【C++篇】面向对象编程的三大特性:深入解析继承机制

目录 一、继承的概念  二、继承的基本定义 2.1 继承的定义格式 2.2 三大继承方式与访问限定符 三、基类与派生类的对象赋值转换 3.1 合法的赋值转换 小tip:子类对象赋值给父类对象不会产生临时变量 3.2 非法的赋值转换 3.3 强制类型转换的注意事项(了解) 四、继承中的作用域 4.1 成员变量的隐藏 4.2 成员函数的隐藏 五、派生类的默认成员函数 5.1 核心规则 5.2 代码演示 问题:为何析构函数的调用顺序是:派生类、基类? 六、继承的特殊场景:友元与静态成员 6.1

By Ne0inhk
【C++经典例题】字符串转整数(atoi)的实现与解析

【C++经典例题】字符串转整数(atoi)的实现与解析

💓 博客主页:倔强的石头的ZEEKLOG主页             📝Gitee主页:倔强的石头的gitee主页             ⏩ 文章专栏:C++经典例题                                   期待您的关注   目录 一、问题描述 二、解题思路 三、代码实现 四、代码逻辑详解 1. 变量初始化 2. 忽略前导空格 3. 处理符号 4. 转换数字 5. 返回结果     一、问题描述 LCR 192. 把字符串转换成整数 (atoi) - 力扣(LeetCode) 在编程中,经常会遇到将字符串转换为整数的需求,就像标准库中的 atoi 函数一样。 本题要求实现一个 myAtoi 函数,将输入的字符串转换为 32 位有符号整数,具体规则如下:   1. 读入字符串并丢弃无用的前导空格。

By Ne0inhk
Effective Modern C++ 条款37:使std::thread在所有路径最后都不可结合

Effective Modern C++ 条款37:使std::thread在所有路径最后都不可结合

Effective Modern C++ 条款37:使std::thread在所有路径最后都不可结合 * 引言:线程生命周期的关键问题 * 线程的两种状态:可结合与不可结合 * 可结合(Joinable)状态的特征 * 不可结合(Unjoinable)状态的四种情况 * 为什么可结合性如此重要? * 两种被拒绝的替代方案 * RAII拯救方案:ThreadRAII类 * ThreadRAII实现详解 * 关键设计决策 * 实际应用案例 * 高级讨论:何时选择join或detach * 性能考量与最佳实践 * 结论:让线程管理无忧 BiliBili上对应的视频为:https://www.bilibili.com/video/BV1iZZgBiE9j 引言:线程生命周期的关键问题 在多线程程序设计中,std::thread的管理是一个看似简单实则暗藏玄机的话题。想象一下,你精心设计的并发程序在大多数情况下运行良好,却在某些边缘情况下突然崩溃——这正是许多开发者在使用原生线程时遇到的噩梦场景。本文将深入探讨std::thread对象

By Ne0inhk