异步IO的用处
首先,当我们创建一个线程用于进行阻塞函数和读取大型文件时,这条线程将会等待结果的反馈,阻塞在原地,那么我们可以说这条线程就有些浪费了,这就叫做同步IO。
那我们有什么方法可以解放这个线程,让其先进行后面的操作,等待结果反馈再回头执行阻塞部分呢,答案就是异步IO,异步IO的原理就是将阻塞操作交给底层操作系统进行,等同于再开一条线程进行这些浪费时间的操作,以效能换取效率,而本该阻塞的线程继续执行后续代码,节省速率。
由此,我们可以发现异步IO的便捷性,但又诞生了其他问题,该如何让线程知道阻塞已经结束,可以回头执行代码了,我们后面再慢慢看。
文件操作api
1.CreateFile 创建/打开文件
2. ReadFile 读取文件
3. WriteFile 写入文件
WriteFileEX
ReadFile
4.OVERLAPPED 重叠I/O结构体
首先我们先创建一个txt文件作为实验
在里面随便输入点字符
然后创建cpp文件
//给出 所有可能的访问权限
//倒数第二个参数填 FILE_FLAG_OVERLAPPED 表明使用异步IO打开文件
HANDLE file=CreateFile("test.txt", GENERIC_ALL, NULL, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
//当一个句柄由异步IO打开,那么
// 句柄变为可等待对象,具有了激发态和非激发态
//创建OVERLAPPED结构体存放异步的数据
OVERLAPPED overlapped{ 0 };
//创建缓冲区存放文件数据
CHAR buff[0x100]{ 0 };
ReadFile(file, buff, 0x100,NULL ,&overlapped);
注意在这里我们无需填入参数获取实际读取长度,因为由于异步IO,一时半会读取不完,那么实际读取参数就没用了
还有我们不能通过ReadFile的返回值判断读取是否成功,不具有参考价值
OVERLAPPED结构体
OVERLAPPED结构体是用于存储异步的数据的
结构体声明:
typedef struct _OVERLAPPED {
ULONG_PTR Internal; //操作系统保留,指出一个和系统相关的状态
ULONG_PTR InternalHigh; //指出发送或接收的数据长度
union {
struct {
DWORD Offset; //文件传送的字节偏移量的低位字
DWORD OffsetHigh; //文件传送的字节偏移量的高位字
};
PVOID Pointer; //指针,指向文件传送位置
};
HANDLE hEvent; //指定一个I/O操作完成后触发的事件
} OVERLAPPED, *LPOVERLAPPED;
在上述代码后面,我们可以执行我们需要做的其他事情,比如打印一些东西什么的,当完成所以其他事情时,我们就可以看看阻塞事件执行完了吗,这里也就是我们提到的让线程知道进行完成的方法
在上面代码中,我们提到了,异步IO会让句柄变为可等待对象,具有了激发态和非激发态
如此我们就不免想到了一个函数WaitForSingleObject,这个函数我们常用于等待线程的完成,我们因为异步IO,也可以等待该句柄的完成
//......
//省略的其他执行代码
//完结后
//等待完成。。。。。
WaitForSingleObject(file, -1);
DWORD numberOfBytes = 0;
//参数3 保存实际读写数
//参数4 是否等待结果出现,由于我们前面使用了WaitForSingleObject,所以这里已经完成,不需要等待
GetOverlappedResult(file, &overlapped, &numberOfBytes, FALSE);
printf("文件内容:%s", buff);
上述内容建立在单线程单文件读取的操作上,如果我们对一个文件多次读取,由于WaitForSingleObject参数1为相同文件句柄,我们怎么判断到底是哪个ReadFile的完成呢
这里就要提到OVERLAPPED里面我们上文忽略的参数
HANDLE hEvent
hEvent作为具有可等待对象,当然可以填充到WaitForSingleObject里面,同时由于我们可以自主给OVERLAPPED命名,他就变成了分辨的不二之选
#include<Windows.h>
#include<iostream>
int main()
{
HANDLE file = CreateFile("test.txt", GENERIC_ALL, NULL, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
OVERLAPPED overlapped{ 0 };
OVERLAPPED overlapped2{ 0 };
CHAR buff[0x100]{ 0 };
CHAR buff2[0x100]{ 0 };
ReadFile(file, buff, 0x100, NULL, &overlapped);
//进行多次读取
ReadFile(file, buff2, 0x100, NULL, &overlapped);
//初始化事件
overlapped.hEvent = CreateEventA(NULL, FALSE, FALSE, NULL);
overlapped2.hEvent = CreateEventA(NULL, FALSE, FALSE, NULL);
//......
//省略的其他执行代码
//完结后
//等待完成。。。。。
WaitForSingleObject(overlapped.hEvent, -1);
WaitForSingleObject(overlapped2.hEvent, -1);
printf("文件内容:%s", buff);
printf("文件内容:%s", buff2);
return 0;
}
但于此便诞生了另一个问题WaitForSingleObject(overlapped.hEvent, -1);
WaitForSingleObject(overlapped2.hEvent, -1);很明显会造成阻塞,如此就有点得不偿失了
我们需要一个方法,不需要手动的等待,让程序自己告诉我们,阻塞已经完成,那就需要使用回调函数
#include<Windows.h>
#include<iostream>
void WINAPI LpoverlappedCompletionRoutine(
DWORD dwErrorCode,
DWORD dwNumberOfBytesTransfered,
LPOVERLAPPED lpOverlapped
)
{
printf("1111");
}
int main()
{
HANDLE file = CreateFile("test.txt", GENERIC_ALL, NULL, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
OVERLAPPED overlapped{ 0 };
OVERLAPPED overlapped2{ 0 };
CHAR buff[0x100]{ 0 };
CHAR buff2[0x100]{ 0 };
//使用ReadFileEx添加回调函数
ReadFileEx(file, buff, 10, &overlapped, LpoverlappedCompletionRoutine);
//进行多次读取
ReadFileEx(file, buff2, 5, &overlapped2, LpoverlappedCompletionRoutine);
//让所在线程进入警醒状态
SleepEx(0, TRUE);
return 0;
}
ReadFileEx将函数放入线程的的函数队列中,如果线程处于警醒模式,在结束后会自动按序运行队列函数,sleepE就会进入警醒模式