select和epoll的区别
当需要读两个以上的I/O的时候,如果使用阻塞式的I/O,那么可能长时间的阻塞在一个描述符上面,另
外的描述符虽然有数据但是不能读出来,这样实时性不能满足要求,大概的解决方案有以下几种:
1. 使用多进程或者多线程,但是这种方法会造成程序的复杂,而且对与进程与线程的创建维护也需要
很多的开销(Apache服务器是用的子进程的方式,优点可以隔离用户);
2. 用一个进程,但是使用非阻塞的I/O读取数据,当一个I/O不可读的时候立刻返回,检查下一个是否
可读,这种形式的循环为轮询(polling),这种方法比较浪费CPU时间,因为大多数时间是不可
读,但是仍花费时间不断反复执行read系统调用;
3. 异步I/O,当一个描述符准备好的时候用一个信号告诉进程,但是由于信号个数有限,多个描述符
时不适用;
4. 一种较好的方式为I/O多路复用,先构造一张有关描述符的列表(epoll中为队列),然后调用一个
函数,直到这些描述符中的一个准备好时才返回,返回时告诉进程哪些I/O就绪。select和epoll这
两个机制都是多路I/O机制的解决方案,select为POSIX标准中的,而epoll为Linux所特有的。
它们的区别主要有三点:
1. select的句柄数目受限,在linux/posix_types.h头文件有这样的声明: #define __FD_SETSIZE
1024 表示select最多同时监听1024个fd。而epoll没有,它的限制是最大的打开文件句柄数目;
2. epoll的最大好处是不会随着FD的数目增长而降低效率,在selec中采用轮询处理,其中的数据结构
类似一个数组的数据结构,而epoll是维护一个队列,直接看队列是不是空就可以了。epoll只会对”
活跃”的socket进行操作—这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那
么,只有”活跃”的socket才会主动的去调用 callback函数(把这个句柄加入队列),其他idle状态
句柄则不会,在这点上,epoll实现了一个”伪”AIO。但是如果绝大部分的I/O都是“活跃的”,每个I/O
端口使用率很高的话,epoll效率不一定比select高(可能是要维护队列复杂);
3. 使用mmap加速内核与用户空间的消息传递。无论是select,poll还是epoll都需要内核把FD消息通知
给用户空间,如何避免不必要的内存拷贝就很重要,在这点上,epoll是通过内核于用户空间
mmap同一块内存实现的。
WindowsAsynchronousSocketChannelImpl
Iocp.OverlappedChannel
UnixAsynchronousSocketChannelImpl
Port.PollableChannelNIO与epoll
上文说到了select与epoll的区别,再总结一下Java NIO与select和epoll:
Linux2.6之后支持epoll
windows支持select而不支持epoll
不同系统下nio的实现是不一样的,包括Sunos linux 和windows
select的复杂度为O(N)
select有最大fd限制,默认为1024
修改sys/select.h可以改变select的fd数量限制
epoll的事件模型,无fd数量限制,复杂度O(1),不需要遍历fd
以下代码基于Java 8。
下面看下在NIO中Selector的open方法:
这里使用了SelectorProvider去创建一个Selector,看下provider方法的实现:
看下 sun.nio.ch.DefaultSelectorProvider.create() 方法,该方法在不同的操作系统中的代码是
不同的,在windows中的实现如下:
在Mac OS中的实现如下:
在linux中的实现如下:
public static Selector open() throws IOException {
return SelectorProvider.provider().openSelector();
}
public static SelectorProvider provider() {
synchronized (lock) {
if (provider != null)
return provider;
return AccessController.doPrivileged(
new PrivilegedAction<SelectorProvider>() {
public SelectorProvider run() {
if (loadProviderFromProperty())
return provider;
if (loadProviderAsService())
return provider;
provider = sun.nio.ch.DefaultSelectorProvider.create();
return provider;
}
});
}
}
public static SelectorProvider create() {
return new WindowsSelectorProvider();
}
public static SelectorProvider create() {
return new KQueueSelectorProvider();
}我们看到create方法中是通过区分操作系统来返回不同的Provider的。其中SunOs就是Solaris返回的是
DevPollSelectorProvider,对于Linux,返回的Provder是EPollSelectorProvider,其余操作系统,返回
的是PollSelectorProvider。