• 水平出发和边缘出发的区别


    在linux的IO多路复用中有水平触发,边缘触发两种模式,这两种模式的区别如下:

        水平触发:如果文件描述符已经就绪可以非阻塞的执行IO操作了,此时会触发通知.允许在任意时刻重复检测IO的状态,没有必要每次描述符就绪后尽可能多的执行IO.select,poll就属于水平触发.

        边缘触发:如果文件描述符自上次状态改变后有新的IO活动到来,此时会触发通知.在收到一个IO事件通知后要尽可能多的执行IO操作,因为如果在一次通知中没有执行完IO那么就需要等到下一次新的IO活动到来才能获取到就绪的描述符.信号驱动式IO就属于边缘触发.

        epoll既可以采用水平触发,也可以采用边缘触发.

        大家可能还不能完全了解这两种模式的区别,我们可以举例说明:一个管道收到了1kb的数据,epoll会立即返回,此时读了512字节数据,然后再次调用epoll.这时如果是水平触发的,epoll会立即返回,因为有数据准备好了.如果是边缘触发的不会立即返回,因为此时虽然有数据可读但是已经触发了一次通知,在这次通知到现在还没有新的数据到来,直到有新的数据到来epoll才会返回,此时老的数据和新的数据都可以读取到(当然是需要这次你尽可能的多读取).

        下面我们还从电子的角度来解释一下:

        水平触发:也就是只有高电平(1)或低电平(0)时才触发通知,只要在这两种状态就能得到通知.上面提到的只要有数据可读(描述符就绪)那么水平触发的epoll就立即返回.

        边缘触发:只有电平发生变化(高电平到低电平,或者低电平到高电平)的时候才触发通知.上面提到即使有数据可读,但是没有新的IO活动到来,epoll也不会立即返回.

    边缘触发(edge-triggered)ET: 每当状态变化时,触发一个事件。

    “举个读socket的例子,假定经过长时间的沉默后,现在来了100个字节,这时无论边缘触发和条件触发都会产生一个read ready notification通知应用程序可读。应用程序读了50个字节,然后重新调用api等待io事件。这时水平触发的api会因为还有50个字节可读,从而立即返回用户一个read ready notification。而边缘触发的api会因为可读这个状态没有发生变化而陷入长期等待。因此在使用边缘触发的api时,要注意每次都要读到socket返回EWOULDBLOCK为止,否则这个socket就算废了。而使用条件触发的api 时,如果应用程序不需要写就不要关注socket可写的事件,否则就会无限次的立即返回一个write ready notification。大家常用的select就是属于水平触发这一类,长期关注socket写事件会出现CPU 100%的毛病。


    1. /************************************************************************* 
    2. > File Name: t_select.c 
    3. > Author: liuxingen 
    4. > Mail: liuxingen@nsfocus.com  
    5. > Created Time: 2014年08月11日 星期一 21时22分32秒 
    6. ************************************************************************/  
    7.   
    8. #include<stdio.h>  
    9. #include<unistd.h>  
    10. #include<sys/types.h>  
    11. #include<sys/time.h>  
    12. #include<sys/select.h>  
    13. #include<string.h>  
    14. #include<errno.h>  
    15.   
    16. int main(int argc, char *argv[])  
    17. {  
    18.     struct timeval timeout;  
    19.     char buf[10];  
    20.     fd_set readfds;  
    21.     int nread, nfds, ready, fd;  
    22.   
    23.     while(1)  
    24.     {  
    25.         timeout.tv_sec = 20L;  
    26.         timeout.tv_usec = 0;  
    27.   
    28.         fd = 0;     //stdin  
    29.         nfds = fd + 1;  
    30.         FD_ZERO(&readfds);  
    31.         FD_SET(fd, &readfds);  
    32.   
    33.         ready = select(nfds, &readfds, NULL, NULL, &timeout);  
    34.         if(ready == -1 && errno == EINTR)  
    35.         {  
    36.             continue;  
    37.         }else if(ready == -1)  
    38.         {  
    39.             fprintf(stderr, "select error:%s ", strerror(errno));  
    40.         }  
    41.         for(fd = 0; fd < nfds; fd++)  
    42.         {  
    43.             if(FD_ISSET(fd, &readfds))  
    44.             {  
    45.                 nread = read(fd, buf, 9);  
    46.                 buf[nread] = '';  
    47.                 puts(buf);  
    48.             }  
    49.         }  
    50.     }  
    51.   
    52.     return 0;  
    53. }  
        上面的示例中每次最多读取9个字节,当我们一次输入了20个字节那么分三次调用select,每次都能立即读取到数据,这也就证明了水平触发中只要数据准备好了那么select都会立即返回.

    1. lxg@remoter:~/station$ ./t_select   
    2. ni hao ma ,wo hen hao a ,ni ne ???  
    3. ni hao ma  
    4.  ,wo hen   
    5. hao a ,ni  
    6.  ne ???  
    7.   
    8. ^C  

    1. /************************************************************************* 
    2. > File Name: demo_sigio.c 
    3. > Author: liuxingen 
    4. > Mail: liuxingen@nsfocus.com  
    5. > Created Time: 2014年08月14日 星期四 21时32分03秒 
    6. ************************************************************************/  
    7.   
    8. #include<stdio.h>  
    9. #include<unistd.h>  
    10. #include<string.h>  
    11. #include<errno.h>  
    12. #include<ctype.h>  
    13. #include<signal.h>  
    14. #include<fcntl.h>  
    15.   
    16. static int g_fd;  
    17.   
    18. static void sigio_handler(int signum)  
    19. {  
    20.     char buf[8] = {0};  
    21.   
    22.     if(read(g_fd, buf, 7) < 0)  
    23.     {  
    24.         fprintf(stderr, "read error:%s ", strerror(errno));  
    25.     }else  
    26.     {  
    27.         printf("sigio recv:%s ", buf);  
    28.     }  
    29. }  
    30. int main(int argc, char *argv[])  
    31. {  
    32.     struct sigaction act;  
    33.     int flags, i = 1, fds[2];  
    34.     pid_t pid;  
    35.   
    36.     if(pipe(fds) < 0)  
    37.     {  
    38.         fprintf(stderr, "pipe error:%s ", strerror(errno));  
    39.         return 1;  
    40.     }  
    41.     if((pid = fork()) > 0)  
    42.     {  
    43.         close(fds[1]);  
    44.         dup2(fds[0], g_fd);  
    45.   
    46.         sigemptyset(&act.sa_mask);  
    47.         act.sa_flags = SA_RESTART;  
    48.         act.sa_handler = sigio_handler;  
    49.         if(sigaction(SIGIO, &act, NULL) == -1)  
    50.         {  
    51.             fprintf(stderr, "sigaction error:%s ", strerror(errno));  
    52.             return 1;  
    53.         }  
    54.   
    55.         if(fcntl(g_fd, F_SETOWN, getpid()) == -1)  
    56.         {  
    57.             fprintf(stderr, "fcntl F_SETOWN error:%s ", strerror(errno));  
    58.             return 1;  
    59.         }  
    60.   
    61.         flags = fcntl(g_fd, F_GETFL);  
    62.         if(fcntl(g_fd, F_SETFL, flags | O_ASYNC | O_NONBLOCK) == -1)  
    63.         {  
    64.             fprintf(stderr, "fcntl F_GETFL error:%s ", strerror(errno));  
    65.             return 1;  
    66.         }  
    67.         while(1)  
    68.         {  
    69.             sleep(10);  
    70.         }  
    71.     }else  
    72.     {  
    73.         char buf[20] = {0};  
    74.         close(fds[0]);  
    75.         for(i = 0; i < 3; i++)  
    76.         {  
    77.             snprintf(buf, 20, "this is loop %d", i);  
    78.             write(fds[1], buf, strlen(buf));  
    79.             printf("loop %d ", i);  
    80.             sleep(3);  
    81.         }  
    82.     }  
    83.   
    84.     return 0;  
    85. }  

        因为信号驱动IO属于边缘触发,所以上面以信号驱动来举例.从下面的输出可以得知:我们一次写入14个字节,但是一次我们每次只读取7字节,除非等到下一次数据写入不然不会再触发SIGIO信号,并且上一次未读完的数据会在下次继续被读取.

    1. lxg@remoter:~/station$ ./demo_sigio   
    2. loop 0  
    3. sigio recv:this is  
    4. loop 1  
    5. sigio recv: loop 0  
    6. loop 2  
    7. sigio recv:this is  
    8. sigio recv: loop 1  
    9. ^C  
  • 相关阅读:
    点击按钮倒计时
    js实现-小框框全选
    CSS文字,文本,背景,盒模型等记录
    xps9560黑苹果展示
    面试——谈谈你对抽象和接口的理解(小知识大考点)
    谈谈你对Java 面向对象思想的理解
    谈谈你对Java 平台的理解
    HashMap1.7 问题总结
    2.3.2 InnoDB内存
    2.3 InnoDB 体系架构
  • 原文地址:https://www.cnblogs.com/duguochao/p/5058182.html
Copyright © 2020-2023  润新知