通俗易懂说多路复用(1)select
多路复用系列:
通俗易懂说多路复用(1)select
https://blog.csdn.net/lqy971966/article/details/89173936
通俗易懂说多路复用(2)epoll
https://blog.csdn.net/lqy971966/article/details/89217648
通俗易懂说多路复用(3)eventfd 事件通知
https://blog.csdn.net/lqy971966/article/details/104751751
通俗易懂说多路复用(4)fcntl
https://blog.csdn.net/lqy971966/article/details/105390106
1. 什么是多路复用?
1.1 复用的概念
复用技术 multiplexing 并不是新技术而是一种设计思想,在通信和硬件设计中存在频分复用、时分复用、波分复用、码分复用等,在日常生活中复用的场景也非常多,因此不要被专业术语所迷惑。
从本质上来说,复用就是为了解决有限资源和过多使用者的不平衡问题,从而实现最大的利用率,处理更多的问题。
1.2 多路复用定义
多路复用就是通过一种机制,可以监听多个描述符,一旦某个描述符就绪(可以读/写),就通过某种方法通知相应程序进行相应操作。
(其中,**文件描述符(fd)**是一个整数,起到一个索引的作用。进程通过fd找到fd指向的文件指针,从而对文件进行操作。)
1.3 通俗易懂的举例说明:多路复用
假设你在家一边玩游戏过程中,水壶正在烧水,洗衣机同时在洗衣服。不久,游戏过程中,水壶响了,你就去倒水;洗衣机洗好了,你就去晾衣服。你通过听它们的响声“滴滴滴”来判断事情有没有完成,然后进行下一步操作。
在这里水壶烧水,洗衣机洗衣服就分别是一个文件描述符;
他们任务完成了,发出“滴滴滴”的响声,就是描述符就绪了,通知你可以进行操作了。
多路复用在这里就是有许多家务在进行着,其中你对多项任务进行监听,通过某种方法来判断它们有没有完成,从而进行下一步操作。
1.4 多路复用种类
多路复用的方法有:select,poll,epoll
2. 什么是select?
2.1 select背景:原始处理方法
1. 原始处理方式:
在select,poll,epoll出现之前,最初处理多路复用的方法是:通过非阻塞忙轮询I/O的方式处理多个描述符(流)
2. 特点: 不停的从头到尾地轮询所有描述符/流
3. 缺点: 如果所有的流都没有数据,cpu空转,浪费cpu资源
4. 伪代码:
while true{ //一直轮询多有流
if i in streams[] { //如果有描述符就绪,就进行处理
read until unavailable
}
}
2.2 select定义
select 是 2000年左右出现的,对外的接口定义:
/* According to POSIX.1-2001 */
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
参考:
https://blog.csdn.net/chinesehuazhou2/article/details/108353712
1. 定义:
为了解决原始处理方式的cpu空转浪费资源问题,引入了一个代理叫做select。select 通过同时监听多个描述符/流,在空闲的时候,把当前处理线程阻塞掉,当有一个或多个描述符/流就绪的时候,就从阻塞状态中醒来进行处理。
2.3 select优缺点及复杂度
2.3.1. 优点:
- select作为先驱对IO复用有巨大的推动,并且指明了后续的优化方向
- 只需要轮询一遍流,就知道有描述符就绪;
2.3.2. 缺点:
- 可协调fd数量和数值都不超过1024 无法实现高并发
- 使用O(n)复杂度遍历fd数组查看fd的可读写性 效率低
- 涉及大量kernel和用户态拷贝 消耗大
- 每次完成监控需要再次重新传入并且分事件传入 操作冗余
2.3.3. 复杂度:*O(n)
2.4 select伪代码
1. 伪代码:
int fds[] = 存放需要监听的socket
while(1){
/*如果fds中的所有socket都没有数据,select会阻塞,
直到有一个socket接收到数据,select返回,唤醒进程。*/
int n = select(..., fds, ...)
/* 遍历fds,通过FD_ISSET判断具体哪个socket收到数据,然后做出处理 */
for(int i=0; i < fds.count; i++){
if(FD_ISSET(fds[i], ...)){
//fds[i]的数据处理
}
}
}
3. 参考:
我读过的最好的epoll讲解 -知乎
https://www.zhihu.com/question/20122137/answer/14049112
https://zhuanlan.zhihu.com/p/64138532
https://zhuanlan.zhihu.com/p/63179839