首页 > 试题广场 >

关于linux的IO复用接口select和epoll,下列

[单选题]
关于linux的I/O复用接口select和epoll,下列说法错误的是()
  • select调用时会进行线性遍历,epoll采用回调函数机制,不需要线性遍历
  • select的最大连接数为FD_SETSIZE
  • select较适合于有大量并发连接,且活跃链接较多的场景
  • epoll较适用于有大量并发连接,但活跃连接不多的场景
  • epoll的效率不随FD数目增加而线性下降
  • epoll通过共享存储实现内核和用户的数据交互
不懂
发表于 2016-06-28 11:22:54 回复(14)
select 和 epoll效率差异的原因:select采用轮询方式处理连接,epoll是触发式处理连接。
Select:
1.Socket数量限制:该限制可操作的Socket数由FD_SETSIZE决定,内核默认32*32=1024.
2.操作限制:通过遍历FD_SETSIZE(1024)个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍。

Epoll
1.Socket数量无限制:该模式下的Socket对应的fd列表由一个数组来保存,大小不限制(默认4k)。
2.操作无限制:基于内核提供的反射模式,有活跃Socket时,内核访问该Socket的callback,不需要遍历轮询。
但当所有的Socket都活跃的时候,所有的callback都被唤醒,会导致资源的竞争。既然都是要处理所有的Socket,
那么遍历是最简单最有效的实现方式。
发表于 2016-05-06 17:11:46 回复(8)
select
  • select能监控的描述符个数由内核中的FD_SETSIZE限制,仅为1024,这也是select最大的缺点,因为现在的服务器并发量远远不止1024。即使能重新编译内核改变FD_SETSIZE的值,但这并不能提高select的性能。
  • 每次调用select都会线性扫描所有描述符的状态,在select结束后,用户也要线性扫描fd_set数组才知道哪些描述符准备就绪,等于说每次调用复杂度都是O(n)的,在并发量大的情况下,每次扫描都是相当耗时的,很有可能有未处理的连接等待超时。
  • 每次调用select都要在用户空间和内核空间里进行内存复制fd描述符等信息。

poll

  • poll使用pollfd结构来存储fd,突破了select中描述符数目的限制。
  • 与select的后两点类似,poll仍然需要将pollfd数组拷贝到内核空间,之后依次扫描fd的状态,整体复杂度依然是O(n)的,在并发量大的情况下服务器性能会快速下降。

epoll

  • epoll维护的描述符数目不受到限制,而且性能不会随着描述符数目的增加而下降。
  • 服务器的特点是经常维护着大量连接,但其中某一时刻读写的操作符数量却不多。epoll先通过epoll_ctl注册一个描述符到内核中,并一直维护着而不像poll每次操作都将所有要监控的描述符传递给内核;在描述符读写就绪时,通过回掉函数将自己加入就绪队列中,之后epoll_wait返回该就绪队列。也就是说,epoll基本不做无用的操作,时间复杂度仅与活跃的客户端数有关,而不会随着描述符数目的增加而下降。
  • epoll在传递内核与用户空间的消息时使用了内存共享,而不是内存拷贝,这也使得epoll的效率比poll和select更高。
发表于 2016-07-26 15:30:41 回复(3)
源自知乎 看完这个就能理解select和epoll的原理,具体关于题目的解析可以参见smartz用户 作者:蓝形参,Geek 伪技术宅  首先我们来定义流的概念,一个流可以是文件,socket,pipe等等可以进行I/O操作的内核对象。     不管是文件,还是套接字,还是管道,我们都可以把他们看作流。     之后我们来讨论I/O的操作,通过read,我们可以从流中读入数据;通过write,我们可以往流写入数据。现在假定一个情形,我们需要从流中读数据,但是流中还没有数据,(典型的例子为,客户端要从socket读如数据,但是服务器还没有把数据传回来),这时候该怎么办? 阻塞:阻塞是个什么概念呢?比如某个时候你在等快递,但是你不知道快递什么时候过来,而且你没有别的事可以干(或者说接下来的事要等快递来了才能做);那么你可以去睡觉了,因为你知道快递把货送来时一定会给你打个电话(假定一定能叫醒你)。 非阻塞忙轮询:接着上面等快递的例子,如果用忙轮询的方法,那么你需要知道快递员的手机号,然后每分钟给他挂个电话:“你到了没?”     很明显一般人不会用第二种做法,不仅显很无脑,浪费话费不说,还占用了快递员大量的时间。     大部分程序也不会用第二种做法,因为第一种方法经济而简单,经济是指消耗很少的CPU时间,如果线程睡眠了,就掉出了系统的调度队列,暂时不会去瓜分CPU宝贵的时间片了。     为了了解阻塞是如何进行的,我们来讨论缓冲区,以及内核缓冲区,最终把I/O事件解释清楚。缓冲区的引入是为了减少频繁I/O操作而引起频繁的系统调用(你知道它很慢的),当你操作一个流时,更多的是以缓冲区为单位进行操作,这是相对于用户空间而言。对于内核来说,也需要缓冲区。 假设有一个管道,进程A为管道的写入方,B为管道的读出方。 假设一开始内核缓冲区是空的,B作为读出方,被阻塞着。然后首先A往管道写入,这时候内核缓冲区由空的状态变到非空状态,内核就会产生一个事件告诉B该醒来了,这个事件姑且称之为“缓冲区非空”。     但是“缓冲区非空”事件通知B后,B却还没有读出数据;且内核许诺了不能把写入管道中的数据丢掉这个时候,A写入的数据会滞留在内核缓冲区中,如果内核也缓冲区满了,B仍未开始读数据,最终内核缓冲区会被填满,这个时候会产生一个I/O事件,告诉进程A,你该等等(阻塞)了,我们把这个事件定义为“缓冲区满”。 假设后来B终于开始读数据了,于是内核的缓冲区空了出来,这时候内核会告诉A,内核缓冲区有空位了,你可以从长眠中醒来了,继续写数据了,我们把这个事件叫做“缓冲区非满”     也许事件Y1已经通知了A,但是A也没有数据写入了,而B继续读出数据,知道内核缓冲区空了。这个时候内核就告诉B,你需要阻塞了!,我们把这个时间定为“缓冲区空”。 这四个情形涵盖了四个I/O事件,缓冲区满,缓冲区空,缓冲区非空,缓冲区非满(注都是说的内核缓冲区,且这四个术语都是我生造的,仅为解释其原理而造)。这四个I/O事件是进行阻塞同步的根本。(如果不能理解“同步”是什么概念,请学习操作系统的锁,信号量,条件变量等任务同步方面的相关知识)。     然后我们来说说阻塞I/O的缺点。但是阻塞I/O模式下,一个线程只能处理一个流的I/O事件。如果想要同时处理多个流,要么多进程(fork),要么多线程(pthread_create),很不幸这两种方法效率都不高。     于是再来考虑非阻塞忙轮询的I/O方式,我们发现我们可以同时处理多个流了(把一个流从阻塞模式切换到非阻塞模式再此不予讨论): while true {     for i in stream[]; {         if i has data             read until unavailable     } }     我们只要不停的把所有流从头到尾问一遍,又从头开始。这样就可以处理多个流了,但这样的做法显然不好,因为如果所有的流都没有数据,那么只会白白浪费CPU。这里要补充一点,阻塞模式下,内核对于I/O事件的处理是阻塞或者唤醒,而非阻塞模式下则把I/O事件交给其他对象(后文介绍的select以及epoll)处理甚至直接忽略。     为了避免CPU空转,可以引进了一个***(一开始有一位叫做select的***,后来又有一位叫做poll的***,不过两者的本质是一样的)。这个***比较厉害,可以同时观察许多流的I/O事件,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有I/O事件时,就从阻塞态中醒来,于是我们的程序就会轮询一遍所有的流(于是我们可以把“忙”字去掉了)。代码长这样: while true {     select(streams[])     for i in streams[] {         if i has data             read until unavailable     } }     于是,如果没有I/O事件产生,我们的程序就会阻塞在select处。但是依然有个问题,我们从select那里仅仅知道了,有I/O事件发生了,但却并不知道是那几个流(可能有一个,多个,甚至全部),我们只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。     但是使用select,我们有O(n)的无差别轮询复杂度,同时处理的流越多,没一次无差别轮询时间就越长。再次 说了这么多,终于能好好解释epoll了     epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll之会把哪个流发生了怎样的I/O事件通知我们。此时我们对这些流的操作都是有意义的。(复杂度降低到了O(1))     在讨论epoll的实现细节之前,先把epoll的相关操作列出: epoll_create 创建一个epoll对象,一般epollfd = epoll_create() epoll_ctl (epoll_add/epoll_del的合体),往epoll对象中增加/删除某一个流的某一个事件 比如 epoll_ctl(epollfd, EPOLL_CTL_ADD, socket, EPOLLIN);//注册缓冲区非空事件,即有数据流入 epoll_ctl(epollfd, EPOLL_CTL_DEL, socket, EPOLLOUT);//注册缓冲区非满事件,即流可以被写入 epoll_wait(epollfd,...)等待直到注册的事件发生 (注:当对一个非阻塞流的读写发生缓冲区满或缓冲区空,write/read会返回-1,并设置errno=EAGAIN。而epoll只关心缓冲区非满和缓冲区非空事件)。 一个epoll模式的代码大概的样子是: while true {     active_stream[] = epoll_wait(epollfd)     for i in active_stream[] {         read or write till     } }     限于篇幅,我只说这么多,以揭示原理性的东西,至于epoll的使用细节,请参考man和google,实现细节,请参阅linux kernel source。
发表于 2016-09-09 15:55:55 回复(4)
答案是C选项。  首先,select就不适合大量的并发操作,一是它的FD数量受限,如何达到“大量”;二是每次调用select需要将全部FD复制到内核,在FD很多时,开销很大。  然后,如果不看FD的数量,只针对活跃链接数的话,select和epoll相比,select更适合活跃链接数多的情况,因为:当活跃链接数多时,不管是select还是epoll,每次均需复制很多FD到内核;但epoll每次在内核还要开辟红黑树和双链表等数据结构,以及注册回调事件,这些操作如果太频繁,反而会降低epoll的效率。
发表于 2016-09-04 11:48:54 回复(0)
select不适合有大量并发连接
编辑于 2016-09-02 20:20:59 回复(0)
光看到这么多选项我就慌了
发表于 2021-06-26 11:05:22 回复(0)
m
发表于 2024-03-02 01:34:23 回复(0)
select、poll和epoll都是常见的I/O复用机制,用于同时监听多个I/O事件。

1. select:select是最古老的I/O复用机制,支持的文件描述符数量有限。它使用fd_set数据结构来管理需要监听的文件描述符集合,通过系统调用select来等待事件发生。当有事件就绪时,select会返回就绪的文件描述符,应用程序可以通过遍历文件描述符集合来判断哪些事件已经就绪。

2. poll:poll是对select的改进,支持的文件描述符数量没有限制。它使用pollfd数据结构来管理需要监听的文件描述符集合,通过系统调用poll来等待事件发生。当有事件就绪时,poll会返回就绪的文件描述符,应用程序可以通过遍历文件描述符集合来判断哪些事件已经就绪。

3. epoll:epoll是在Linux系统上引入的高性能I/O复用机制,支持的文件描述符数量没有限制。它使用epoll_event数据结构来管理需要监听的文件描述符集合,通过系统调用epoll_ctl来添加、修改或删除文件描述符。当有事件就绪时,epoll会返回就绪的文件描述符,应用程序可以通过遍历就绪事件来处理。

相比select和poll,epoll具有以下优点:
- 高效:epoll使用红黑树和事件驱动的方式来管理文件描述符,可以高效地处理大量的并发连接。
- 高扩展性:epoll支持边缘触发(EPOLLET)和水平触发(EPOLLIN、EPOLLOUT),可以根据需求灵活选择。
- 零拷贝:epoll支持零拷贝技术,可以将数据直接从内核缓冲区复制到用户缓冲区,减少数据拷贝的开销。

在使用I/O复用机制时,需要根据具体的应用场景和需求选择合适的机制,并根据系统资源和性能要求进行配置。
发表于 2023-09-24 17:48:45 回复(0)
表示忘了以前怎么学的了
发表于 2023-03-03 16:38:10 回复(0)
理解不是很深刻
发表于 2022-08-02 14:34:24 回复(0)
epoll应该没有使用回调函数
发表于 2020-08-06 12:07:12 回复(0)
有大量并发连接的情况下,就不可能是在活跃连接较多的场景
发表于 2020-07-19 15:35:10 回复(0)
select默认最多并发连接数为1024个,不适合大量并发吧,除非修改默认的FD_SETSIZE
发表于 2019-05-05 17:06:29 回复(0)
既然select适合轮询方式处理连接,所以它需要不断遍历,这样看来活动链一旦多了岂不是花费很长的时间
发表于 2019-04-12 21:04:11 回复(0)
select select能监控的描述符个数由内核中的FD_SETSIZE限制,仅为1024,这也是select最大的缺点,因为现在的服务器并发量远远不止1024。即使能重新编译内核改变FD_SETSIZE的值,但这并不能提高select的性能。 每次调用select都会线性扫描所有描述符的状态,在select结束后,用户也要线性扫描fd_set数组才知道哪些描述符准备就绪,等于说每次调用复杂度都是O(n)的,在并发量大的情况下,每次扫描都是相当耗时的,很有可能有未处理的连接等待超时。 每次调用select都要在用户空间和内核空间里进行内存复制fd描述符等信息。 poll poll使用pollfd结构来存储fd,突破了select中描述符数目的限制。 与select的后两点类似,poll仍然需要将pollfd数组拷贝到内核空间,之后依次扫描fd的状态,整体复杂度依然是O(n)的,在并发量大的情况下服务器性能会快速下降。 epoll epoll维护的描述符数目不受到限制,而且性能不会随着描述符数目的增加而下降。 服务器的特点是经常维护着大量连接,但其中某一时刻读写的操作符数量却不多。epoll先通过epoll_ctl注册一个描述符到内核中,并一直维护着而不像poll每次操作都将所有要监控的描述符传递给内核;在描述符读写就绪时,通过回掉函数将自己加入就绪队列中,之后epoll_wait返回该就绪队列。也就是说,epoll基本不做无用的操作,时间复杂度仅与活跃的客户端数有关,而不会随着描述符数目的增加而下降。 epoll在传递内核与用户空间的消息时使用了内存共享,而不是内存拷贝,这也使得epoll的效率比poll和select更高。
发表于 2019-03-06 13:30:33 回复(0)
C 选项 select 最好是在1024以下的连接,且活跃度高的场景下应用
发表于 2018-09-20 23:04:08 回复(0)
1. epoll内核实现没有使用共享内存(至少对2.6以后内核来说是这样)
2. select()/poll()的确存在惊群,而epoll对于ET模式且为同一个epoll实例的调用不存在惊群
3. epoll使用红黑树存储事件,因此效率是O(logn)

发表于 2018-05-11 18:38:23 回复(0)
select 模式需要轮询,也就是得一个一个找,效率比较低
发表于 2017-05-18 21:18:04 回复(0)
有点像Java里面的NIO
发表于 2016-09-22 10:40:31 回复(0)