异步IO
- 异步IO
- 异步I/0注意事项:
- 定位问题
- 总解决方案
- APC调用队列
异步IO
当我们读取一个文件时,一般情况下,线程是阻塞的,也就是说,当前线程在等待文件读取操作结束,这种方式叫同步IO。
Windows 在系统底层为用户实现了另外一种高效的机制,叫重叠I/0,又称作异步I/0
。异步I/0提供了这样一种功能,当用户读取文件的时候,读取文件函数会立马返回结果不会阻塞线程,但是实际上文件并没有读取完,而是交给了系统底层自动去处理,这样文件的读取操作就不会阻塞住你的线程。但是这种引发了一个问题,我们如何才能知道文件已经读取完毕了呢? I
异步I/0注意事项:
一旦一个句柄是以异步I/0的方式打开的,那么:
1、句柄变为可等待的对象,就是说,它具有了激发态和非激发态。
2、文件指针这个东西就失效了,需要用overlapped结构体中的offset表示读取或写入的位置。
定位问题
异步IO存在问题,如果不止通过一个句柄进行读操作还有其他操作,则GetOverlappedResult函数无法定位到是哪一个操作。
#include<iostream>
#include<Windows.h>
int main()
{
//用异步IO方式打开一个文件
HANDLE hFile = CreateFileW(L"D:\\code\\VisualStudio2022\\异步Io\\异步Io\\test.txt", GENERIC_ALL, NULL, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);//FILE_FLAG_OVERLAPPED异步IO
CHAR buff[0X100]{ 0 };
OVERLAPPED overlapped{ 0 };
//读取文件内容
ReadFile(hFile, buff, 0X100, NULL, &overlapped);
DWORD numberOfBytes = 0;
WaitForSingleObject(hFile, -1);
GetOverlappedResult(hFile, &overlapped, &numberOfBytes, FALSE);
printf("文件内容:%s\n", buff);
printf("实际完成IO数:%d\n", numberOfBytes);
CloseHandle(hFile);
return 0;
}
解决无法精确定位是哪个API完成的问题。新问题是,到最后还是要调用WaitForSingleObject函数,如果操作过大还是会导致卡顿,无法流畅体验。
#include<iostream>
#include<Windows.h>
int main()
{
HANDLE hFile = CreateFile(L"D:\\code\\VisualStudio2022\\异步Io\\异步Io\\test.txt", GENERIC_ALL, NULL, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
OVERLAPPED overlapped{ 0 };
OVERLAPPED overlapped1{ 0 };
overlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
overlapped1.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
char buff[0X100]{ 0 };
char buff1[0X100]{ 0 };
ReadFile(hFile, buff, 10, NULL, &overlapped);
ReadFile(hFile, buff1, 20, NULL, &overlapped1);
//精确地判断哪个结束
WaitForSingleObject(overlapped.hEvent, -1);
WaitForSingleObject(overlapped1.hEvent, -1);
printf("%s\n", buff);
printf("%s\n", buff1);
return 0;
}
总解决方案
APC调用队列
每个线程都维护了一个APC(异步过程调用)队列,队列中的每一项都是函数,当一个线程处于可警醒(闲暇)状态时,线程会遍历自己的APC队列并调用所有的函数,直到结束,再恢复执行。
每当有一个异步IO请求完成的时候,ReadFileEx,就会像APC队列中添加相应的函数和对应的参数,添加的顺序和投递的顺序不一定相同,哪个先处理完就先投递哪个。
通过ReadFileEx函数,让每一个异步IO完成后都向APC队列中加入函数及相应参数,来判断线程顺序的同时不使用WaitForSingleObject等待激发且抢占不影响流畅性。
#include<iostream>
#include<Windows.h>
void WINAPI overLappedCompletionProc(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped)
{
if (lpOverlapped->hEvent == (HANDLE)0X100)
{
printf("1完成了\n");
}
else {
printf("2完成了\n");
}
}
int main()
{
HANDLE hFile = CreateFile(L"D:\\code\\VisualStudio2022\\异步Io\\异步Io\\test.txt", GENERIC_ALL, NULL, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
OVERLAPPED overlapped{ 0 };
OVERLAPPED overlapped1{ 0 };
//overlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
//overlapped1.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
overlapped.hEvent = (HANDLE)0X100;
overlapped1.hEvent = (HANDLE)0X200;
char buff[0X100]{ 0 };
char buff1[0X100]{ 0 };
//当有一个异步IO请求完成后,就会向APC队列中添加相应的函数和对应的参数
ReadFileEx(hFile, buff, 10, &overlapped, overLappedCompletionProc);
ReadFileEx(hFile, buff1, 20, &overlapped1, overLappedCompletionProc);//当进程处于激发态时触发,通过Sleep让进程处于激发态
SleepEx(0, TRUE);
//ReadFile(hFile, buff, 10, NULL, &overlapped);
//ReadFile(hFile, buff1, 20, NULL, &overlapped1);
//精确地判断哪个结束
//WaitForSingleObject(overlapped.hEvent, -1);
//WaitForSingleObject(overlapped1.hEvent, -1);
printf("%s\n", buff);
printf("%s\n", buff1);
return 0;
}