分析epoll机制之前我们需要先明确一个概念什么是阻塞和非阻塞?
举个例子,假设你要收个快递,但你不知道快递小哥什么时候送,你又没有其他事可做,那么你可以去睡觉了(释放cpu资源),因为你知道快递小哥到了会打电话通知你,这个睡觉的状态就是阻塞。
同样,你要收个快递,但你不知道快递小哥什么时候送,但你放心不下这件事你不选择去睡觉,你每分钟都给快递小哥打电话问他到了没,你一直打电话询问这个状态就是非阻塞式忙轮询,虽然最终都收到了快递,但后者会很累(cpu一直在运行)java并发编程中的CAS也是这种原理。
而阻塞是一个和 I/O 相关的概念,这涉及一个操作系统级的操作epoll_wait,当读取磁盘文件时会有内存速度不匹配的问题,中间需要有个缓存过程,是从磁盘读到缓存,缓存满了,再从缓存中读数据,等待缓存满的时候就需要阻塞住先不读取。
再举个例子,当前有n个I/O事件,我们如何处理多个流?
没有阻塞概念的话,如下(是一段伪代码):
while(true){
for(i -> stream[]){
// 一直询问我可以读了吗?
if i has data{
read data until unalivaible
}
}
}
这种方式下,当所有的流都没数据的这段时间里,cpu只能一直跑for循环,但又没有数据可读,所以cpu一直在空转,耗费cpu资源而没有任何意义,那怎么解决这个问题呢?
阻塞就出现了:
while(true){
//select是个Linux系统级操作,他会一直阻塞直到其中有一个或多个流可以读,再去执行下面的代码
select(stream[])
for(i -> stream[]){
// 一直询问我可以读了吗?
if i has data{
read data until unalivaible
}
}
}
但是这个方式并不能确切的知道有几个流可读,我们只能无差别的去轮询所有的流,那怎么处理这个缺陷呢?随着Linux系统的升级,今天的主角epoll就出现了:
while(true){
//select是个Linux系统级操作,他会一直阻塞直到其中有一个或多个流可以读,再去执行下面的代码
active_stream = epoll_wait(stream[])
for(i -> active_stream[]){
read data until unalivaible
}
}
epoll可以过滤出可以进行读写的流,而不用我们再去轮询了,此时我们对所有流的处理都是有意义的。
epoll是Linux内核中的一种可扩展IO事件处理机制。大量应用程序请求时能够获得较好的性能。
下面是几个epoll相关的几个方法:
int epoll_create(int size);
//创建一个epoll的句柄,size用来告诉内核需要监听的数目一共有多大
int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event)
//epoll的事件注册函数
int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout)
//参数events用来放从内核得到事件的集合,maxevents告知这个events有多大,这个maxevents的值不能
//大于创建epoll_create()时的size,timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法是永久阻塞)
到此epoll是什么大家已经有了基本概念和认知,下面再结合我们熟悉的Handler看看在Handler中是如何使用的:
MessageQueue.java
//epoll的句柄,通过他能找到对应的epoll
private long mPtr;
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
//native层去创建epoll
mPtr = nativeInit();
}
//以下是epoll相关的native方法
private native static long nativeInit();//会调用epoll_create()
private native static void nativeDestroy(long ptr);
@UnsupportedAppUsage
private native void nativePollOnce(long ptr, int timeoutMillis); //会调用epoll_wait(),这个函数里timeout就是这里传进去的
private native static void nativeWake(long ptr); //唤醒阻塞
private native static boolean nativeIsPolling(long ptr);
private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
epoll的了解就先到此为止了。