七叶笔记 » golang编程 » linux下aio异步读写详解与实例《完整版》

linux下aio异步读写详解与实例《完整版》

1.为什么会有异步I/O

aio异步读写是在linux内核2.6之后才正式纳入其标准。之所以会增加此模块,是因为众所周知我们计算机CPU的执行速度远大于I/O读写的执行速度,如果我们用传统的阻塞式或非阻塞式来操作I/O的话,那么我们在同一个程序中(不用多线程或多进程)就不能同时操作俩个以上的文件I/O,每次只能对一个文件进行I/O操作,很明显这样效率很低下(因为CPU速度远大于I/O操作的速度,所以当执行I/O时,CPU其实还可以做更多的事)。因此就诞生了相对高效的异步I/O

2.异步I/O的基本概念

所谓异步I/O即我们在调用I/O操作时(读或写)我们的程序不会阻塞在当前位置,而是在继续往下执行。例如当我们调用异步读API aio_read()时,程序执行此代码之后会接着运行此函数下面的代码,并且与此同时程序也在进行刚才所要读的文件的读取工作,但是具体什么时候读完是不确定的

3.异步aio的基本API

上述的每个API都要用aiocb 结构体 赖进行操作
aiocb的结构中常用的成员有

4异步I/O操作的具体使用

(1)异步读aio_read

aio_read函数请求对一个文件进行读操作,所请求文件对应的 文件描述符 可以是文件,套接字,甚至管道其原型如下

int aio_read(struct aiocb *paiocb);

1

该函数请求对文件进行异步读操作,若请求失败返回-1,成功则返回0,并将该请求进行排队,然后就开始对文件的异步读操作

需要注意的是,我们得先对aiocb结构体进行必要的初始化

具体实例如下

aio_read

上述实例中aiocb结构体用来表示某一次特定的读写操作,在异步读操作时我们只需要注意4点内容

1.确定所要读的文件描述符,并写入aiocb结构体中(下面几条一样不再赘余)

2.确定读所需的缓冲区

3.确定读的字节数

4.确定文件的 偏移量

总结以上注意事项:基本上和我们的read函数所需的条件相似,唯一的区别就是多一个文件偏移量

值得注意的是上述代码中aio_error是用来获取其参数指定的读写操作的状态的

其原型如下

int aio_error(struct aiocb *aiopcb);

1

当其状态处于EINPROGRESS则I/O还没完成,当处于ECANCELLED则操作已被取消,发生错误返回-1

而aio_return则是用来返回其参数指定I/O操作的返回值

其原型如下

ssize_t aio_return(struct aiocb *paiocb);

1

如果操作没完成调用此函数,则会产生错误

特别提醒在编译上述程序时必须在编译时再加一个-lrt

上述代码运行结果如下

aio_writr用来请求异步写操作

其函数原型如下

int aio_write(struct aiocb *paiocb);

1

aio_write和aio_read函数类似,当该函数返回成功时,说明该写请求以进行排队(成功0,失败-1)

其和aio_read调用时的区别是就是我们如果在打开文件是,flags设置了O_APPEND则我们在填充aiocb时不需要填充它的偏移量了

具体实例如下

具体运行结果请读者自己去试试

(3)使用aio_suspend阻塞异步I/O

aio_suspend函数可以时当前进程挂起,知道有向其注册的异步事件完成为止

该函数原型如下

int aio_suspend(const struct aiocb *const cblist[],int n,const struct timespec *timeout);

1

第一个参数是个保存了aiocb块地址的数组,我们可以向其内添加想要等待阻塞的异步事件,第二个参数为向cblist注册的aiocb个数,第三个参数为等待阻塞的超时事件,NULL为无限等待

具体使用如下

suspend:

(4)lio_listio函数

aio同时还为我们提供了一个可以发起多个或多种I/O请求的接口lio_listio

这个函数效率很高,因为我们只需一次系统调用(一次内核上下位切换)就可以完成大量的I/O操作

其函数原型如下

int lio_listio(int mode,struct aiocb *list[],int nent,struct sigevent *sig);

1

第一个参数mode可以有俩个实参,LIO_WAIT和LIO_NOWAIT,前一个会阻塞该调用直到所有I/O都完成为止,后一个则会挂入队列就返回

具体实例如下

lio_listio

# include <stdio.h>

#include<sys/socket.h>

#include<netinet/in.h>

#include<arpa/inet.h>

#include<assert.h>

#include<unistd.h>

#include<stdlib.h>

#include<errno.h>

#include<string.h>

#include<sys/types.h>

#include<fcntl.h>

#include<aio.h>

#define BUFFER_SIZE 1025

int MAX_LIST = 2;

int main(int argc, char **argv)

{

struct aiocb *listio[2];

struct aiocb rd,wr;

int fd,ret;

//异步读事件

fd = open (“test1.txt”,O_RDONLY);

if(fd < 0)

{

perror (“test1.txt”);

}

bzero(&rd, sizeof (rd));

rd.aio_buf = (char *) malloc (BUFFER_SIZE);

if(rd.aio_buf == NULL)

{

perror(“aio_buf”);

}

rd.aio_fildes = fd;

rd.aio_nbytes = 1024;

rd.aio_offset = 0;

rd.aio_lio_opcode = LIO_READ; ///lio操作类型为异步读

//将异步读事件添加到list中

listio[0] = &rd;

//异步些事件

fd = open(“test2.txt”,O_WRONLY | O_APPEND);

if(fd < 0)

{

perror(“test2.txt”);

}

bzero(&wr,sizeof(wr));

wr.aio_buf = (char *)malloc(BUFFER_SIZE);

if(wr.aio_buf == NULL)

{

perror(“aio_buf”);

}

wr.aio_fildes = fd;

wr.aio_nbytes = 1024;

wr.aio_lio_opcode = LIO_WRITE; ///lio操作类型为异步写

//将异步写事件添加到list中

listio[1] = ≀

//使用lio_listio发起一系列请求

ret = lio_listio(LIO_WAIT,listio,MAX_LIST,NULL);

//当异步读写都 完成时 获取他们的返回值

ret = aio_return(&rd);

printf (“\n读返回值:%d”,ret);

ret = aio_return(&wr);

printf(“\n写返回值:%d”,ret);

return 0;

5.I/O完成时进行异步通知

当我们的异步I/O操作完成之时,我们可以通过信号通知我们的进程也可用 回调函数 来进行异步通知,接下来我会为大家主要介绍以下回调函数来进行异步通知,关于信号通知有兴趣的同学自己去学习吧

使用回调进行异步通知

该种通知方式使用一个系统回调函数来通知应用程序,要想完成此功能,我们必须在aiocb中设置我们想要进行异步回调的aiocb指针,以用来回调之后表示其自身

实例如下

aio 线程 回调通知

线程会掉是通过使用aiocb结构体中的aio_sigevent结构体来控制的,
其定义如下

资料内容包括:C/C++,Linux,golang,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,嵌入式 等。。。后台私信;资料;两个字可以免费领取

相关文章