多路IO复用与异步IO
什么时多路IO复用? 多路IO复用是一种同步IO模型,,它可以实现一个线程可以同时监视多个文件描述符,一旦有某个文件描述符准备就绪,就会通知应用程序,对该文件描述符进行操作。在监视的各个文件描述符没有准备就绪的时候,应用程序线程就会阻塞,交出cpu,让cpu去执行其他的任务,以此提高cpu的利用率。
同步阻塞IO(BIO)与同步非阻塞IO(NIO)
同步阻塞IO
关于同步阻塞IO可以打个比方就是我们要读取键盘和鼠标但是由于read()函数的阻塞作用导致我们只能读取按照程序编写的读取顺序来读取两个设备的数据,因为如果read()函数正在阻塞读取键盘数据,但是此时我们没有键盘输入,去动鼠标,鼠标就会返回数据,但是由于read()函数阻塞在了键盘读取上,所以我们就错过了读取鼠标数据的机会。
同步非阻塞IO
而同步非阻塞则是在一个while()循环里面使用设置了非阻塞的read()函数读取键盘和鼠标数据,如果没有读取到数据就会立即返回错误,然后继续读取鼠标和键盘,直到两个设备有数据来就会成功读取到数据。
多路IO复用
单线程使用select()/poll()等系统调用实现对多个文件句柄的监视,如果有某个文件句柄的事件发生就会返回,没有的话就会阻塞等待,单其实select()和poll()都是表现为外部阻塞式,内部非阻塞式自动轮询多路阻塞式IO。
select()函数使用示例
单个线程打开的FD是有限的,通常默认是1024个,可以通过FD_SETSIZE设置
每次调用select()都需要把fd集合从用户态拷贝到内核态,在需要轮询的fd很多时,会造成较大的性能开销。
select()前面提到了select()是通过轮询扫描检测事件有没有发生的所以执行效率比较低。
代码演示
在设置了select()函数的超时时间后,当函数超时后就会返回,后面再次执行到select()函数时,select()并不会阻塞了,不管有没有监视的文件句柄事件发生,select()函数都会返回超时结果。
也可应个select()的时间设置参数地址给NULL,这样select()函数就会不断地监视文件句柄。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/time.h>
int main(void)
{
int fd=-2;
char buf[20];
fd_set set;
struct timeval tm;
bzero(buf,sizeof(buf));
fd=open("/dev/input/mouse0",O_RDONLY);
if (fd<0)
{
perror("open:");
}
printf("already read\n");
tm.tv_sec=10; //设置等待超时时间
tm.tv_usec=0;
FD_ZERO(&set); //清空set变量
FD_SET(fd,&set); //将需要监视的fd加入到select()的见识列表
FD_SET(0,&set);
while (1)
{
int ret=select(fd+1,&set, NULL,NULL, &tm);
if (ret<0)
{
perror("why");
}
else if (ret==0)
{
printf("等待超时\n");
}
else
{
if(FD_ISSET(fd,&set)) //判断时哪一个被监视的文件句柄事件发生了
{
read(fd,buf,5);
printf("read mouse0 buf=%s\n",buf);
}
if(FD_ISSET(0,&set))
{
read(0,buf,5);
printf("read keyborad buf=%s\n",buf);
}
}
}
return 0;
}
poll()函数使用示例
poll()函数与select()函数一样每次调用都需要把fd集合从用户态拷贝到内核态,在需要轮询的fd很多时,会造成较大的性能开销。
poll()对文件句柄的监视也是和select()函数一样采用线性扫描的方式进行轮询,所以在高并发的情况下效率也是比较底下的。
但是poll()与select()不同的一点就是poll没有监视数量的限制
代码演示
在poll的使用过程中当程序开始运行等待超时后,如果发生IO事件后还能够检测到,但是当检测到IO事件和poll()就不会再超时了,就会一直等待监听事件的发生。这一点和select()有所不同,select()超时后就不在监听IO事件的发生了,而是一直返回超时。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/time.h>
#include <signal.h>
#include <poll.h>
int main(void)
{
int fd=-2;
char buf[20];
struct pollfd pfd[2]={0}; //监视两个文件句柄所以定义了一个结构体数组
bzero(buf,sizeof(buf));
fd=open("/dev/input/mouse0",O_RDONLY);
if (fd<0)
{
perror("open:");
}
printf("already read\n");
pfd[0].fd=0; //监视的文件描述符
pfd[0].events=POLLIN; //设置为监视读取事件
pfd[1].fd=fd;
pfd[1].events=POLLIN;
while (1)
{
int ret=poll(pfd,fd+1,1000);
if (ret<0)
{
perror("why");
}
else if (ret==0)
{
printf("等待超时\n");
}
else
{
if(pfd[0].events==pfd[0].revents) //判断是否为监视的事件发生
{
bzero(buf,sizeof(buf));
read(0,buf,5);
printf("read keyboard data=%s\n",buf);
}
if (pfd[1].events==pfd[1].revents)
{
bzero(buf,sizeof(buf));
read(fd,buf,5);
printf("read mouse data=%s\n",buf);
}
}
}
return 0;
}
异步IO
异步IO相当于系统内核为我们实现的一套软件中断程序,如下代码中的function()函数就可以比喻为中断服务函数。主程序在完成一些异步IO的初始化之后,在while()循环中检测键盘的输入,但是如果注册了异步事件的IO事件发生,就会去执行异步任务函数,即获取鼠标输入的数据。
代码演示
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
int fd=0;
void function(int sig)
{
char buf[5]={0};
if (sig!=SIGIO)
{
return;
}
else
{
read(fd,buf,sizeof(buf));
printf("read mouse data=%s\n",buf);
}
}
int main(void)
{
char buf[20];
int flag=-1;
bzero(buf,sizeof(buf));
fd=open("/dev/input/mouse0",O_RDONLY);
if (fd<0)
{
perror("open:");
}
printf("already read\n");
//把鼠标的文件描述符设置为可以接收一部IO
flag=fcntl(fd,F_GETFL);
flag|=O_ASYNC;
fcntl(fd,F_SETFL,flag);
//把异步IO事件的接收进程设置为当前进程
fcntl(fd,F_SETOWN,getpid());
//注册当前进程的SINGIO信号捕获函数
signal(SIGIO,function);
while (1)
{
memset(buf,0,sizeof(buf));
read(0,buf,sizeof(buf));
printf("read keynoard =%s\n",buf);
}
return 0;
}