从select的一个死循环谈epoll的ET模式

1240阅读 0评论2015-03-20 B_C_1024
分类:LINUX

select的一个死循环谈epollET模式

                                                                                                                                                              ——作者:lvyilong316
最近写程序遇到一个问题,就是发现select监听标准输出的时候遇到了死循环,具体程序如下程序一。程序的意图是每当用户在控制台有任何输入,就输出hello world

程序一:

#include 

#include 

#include 

#include 

int 

main(int argc, char *argv[])

{

    int maxfdp1;

char buf[256];

fd_set rset;

    maxfdp1=STDIN_FILENO+1;

for(;;)

FD_ZERO(&rset);

FD_SET(STDIN_FILENO,&rset);

select(maxfdp1,&rset,NULL,NULL,NULL);

if(FD_ISSET(STDIN_FILENO,&rset))

    {

printf("hello world!\n");

    }

}

return 0;

}

运行结果:

结果会循环输出“hello world”,这是为什么呢?也就是当我们输入任意字符后,select每次都判断标准输入的描述符就绪。造成这种情况的原因要从select的机制说起。如下图所示:

没一个文件描述符(fd)与一个缓冲关联,selectfd的监听其实就是监听fd的缓冲,当缓冲中有数据要读的时候,select就认为该fd可读就绪,当缓冲中有数据待写的时候,select就认为该fd可写就绪。

下面我们在分析一下我们的程序一,当输入任意字符,比如:“abc”,则“abc”被放在标准输入的缓冲当中,此时缓冲中有数据(abc)待读,所以select返回STDIN_FILENO就绪,程序输出“hello World!”。紧接着进入下一次循环,select重新将STDIN_FILENO加入监听的描述符集,由于刚刚的“abc”并没有被读出,所以仍在缓冲中,此时STDIN_FILENO的缓冲中仍有数据等待读,所以select又返回STDIN_FILENO可读就绪,又一次输出“hello world!”。

之后循环情况类似,由于缓冲的的“待读”数据始终还在,所以每次select都直接返回STDIN_FILENO就绪,每次都输出“hello world!”,这就是造成程序死循环的原因。那么让偶们如何解决呢?

     方法一:将缓冲区中的“待读”数据读出,程序修改如下所示:

        if(FD_ISSET(STDIN_FILENO,&rset))

    {

read(STDIN_FILENO,buf,sizeof(buf));//将缓冲区的数据读出(读入buf数组)

printf("hello world!\n");

    }

修改后程序正常运行(任意输入后,输出“hello world!”)。我们在做如下实验——输入多个字符,制度出部分字符。修改程序做如下修改:

                char buf[2];//buf长度改为2,每次从缓冲区读入两个字符

程序运行结果如下:

分析:

(1) 输入一个字符s,s被放入缓冲区,同时放入缓冲区中的还有换行符\n,缓冲区中有待读数据,select返回读就绪,reads\n读出,缓冲区清空,select再次阻塞。

(2) 输入两个dd,缓冲区中的数据变为dd\nselect返回读就绪,read读出两个字符——ss,输出hello world!此时缓冲区中还有\n,所以下一次select依然返回读就绪,之后read\n读出,输出hello world!,缓冲区清空,select阻塞。

(3) 之后输入三个字符,四个字符的情况类似,不在分析。

到此,我们将程序一基本分析清楚,但我们的讨论远没有结束。因为,这个程序的现象令我想起了另一个知识点——epollLTET模式。

关于ETLT模式的介绍,之前的博文已经写得很详细了,这里不再重复。这里想说的是,以上程序一的现象正式LT模式的一个典型实例,也是LT模式的一个缺陷。我们知道selectpoll都是采用LT模式,并且只有这一种模式。所以,使用select或者poll要想解决程序一的问题只能采用方法一。下面我们采用epollET模式解决,也就是方法二。代码如下。

程序二:

#include 

#include 

#include 

using namespace std;

int main(void)

{

int epfd,nfds;

struct epoll_event ev,events[5];//ev用于注册事件,数组用于返回要处理的事件

    epfd=epoll_create(1);//只需要监听一个描述符——标准输入

ev.data.fd=STDIN_FILENO;

ev.events=EPOLLIN|EPOLLET;//监听读状态同时设置ET模式

epoll_ctl(epfd,EPOLL_CTL_ADD,STDIN_FILENO,&ev);//注册epoll事件

for(;;)

{

nfds=epoll_wait(epfd,events,5,-1);

for(int i=0;i

{

if(events[i].data.fd==STDIN_FILENO)

               cout<<"hello world!"<

}

}

}

运行结果:

可以发现,使用ET模式,程序正常运行,虽然输入缓冲区的数据并没有被读出,但是只要没有新的数据进入,epoll就不再被通知(只被通知一次),当再次输入数据,又有新的数据进入缓冲时才会触发epoll,再次返回读就绪,输出hello world

我们再看看使用LT模式的情况,将程序二以下修改:

    ev.events=EPOLLIN;//默认使用LT模式

运行结果:

可以发现和select的结果一样。

上一篇:linux CFS调度算法
下一篇:阿里巴巴2015校园招聘算法题