在多路复用的IO的模型中,存在三种机制,分别是select
,poll
和epoll
.为了便于理解,可以使用简单的伪代码来表示一个原始的IO的读写:
1 2 3 4 5 6 7 8 9 10
| while(true) { for(Stream i: streamArr) { if(i.isNotReady()){ continue; } doSomething(); } }
|
select
时间复杂度O(n),它仅仅知道了,有I/O事件发生了,却并不知道是哪那几个流(可能有一个,多个,甚至全部),我们只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。所以select具有O(n)的无差别轮询复杂度,同时处理的流越多,无差别轮询时间就越长。 具体的伪代码如下:
1 2 3 4 5 6 7 8 9 10 11
| while(true) { getSelectReadyStream(); for(Stream i: streamArr) { if(i.isNotReady()){ continue; } doSomething(); } }
|
**select的缺点:**
(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
(3)select支持的文件描述符数量太小了,默认是1024
poll
时间复杂度O(n),poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态, 但是它没有最大连接数的限制,原因是它是基于链表来存储的.而select是基于set的数据结构。
epoll
时间复杂度O(1),**epoll可以理解为event poll**,不同于忙轮询和无差别轮询,epoll会把哪个流发生了怎样的I/O事件通知我们。所以我们说epoll实际上是事件驱动(每个事件关联上fd)的,此时我们对这些流的操作都是有意义的。(复杂度降低到了O(1)) ,具体的伪代码如下:
1 2 3 4 5 6 7 8 9
| while(true) { streamArr=getEpollReadyStream(); for(Stream i: streamArr) { doSomething(); } }
|
综上,在选择select,poll,epoll时要根据具体的使用场合以及这三种方式的自身特点。