1. 程式人生 > >Windows異步I/O詳解

Windows異步I/O詳解

Windows;異步;IO;

當我們對文件進行讀寫時,線程本該是阻塞的,即線程在等待讀寫操作的結束,這種方式稱為同步I/O。Windows在系統層為我們提供了一種高效的機制——異步IO
異步IO提供了這樣一種功能:當你讀取文件時,讀取函數會立刻返回,讀取任務轉交給系統底層自動處理,這樣文件的讀取操作就不會阻塞線程。IO操作完成時,由系統發出完成通知。

介紹三種完成通知:
1.直接等待句柄(文件等待對象)
2.等待OVERLAPPED中的hEvent句柄(等待事件對象)
3.異步調用(APC調用)

主要函數:CreateFile();ReadFile();WriteFile();
數據成員:OVERLAPPED結構體。

一個句柄如果是以異步IO的方式打開,那這個句柄就具有了兩個特性


1.文件變為可等待的對象(即具有激發態和非激發態)
2.文件指針失效(需要通過OVERLAPPED結構體中的的Offect表示讀取或寫入的位置,而不能用SetFilePointer()這樣的函數。)

代碼實例:(VS2015控制臺)
準備:Debug目錄下創建123.exe並保存數據

1.直接等待句柄

#include "stdafx.h"
#include <windows.h>
typedef struct _MYOVERLAPPED{
    OVERLAPPED ol;
    HANDLE hFile;
    PBYTE pBuf;
    int nIndex;
}MYOVERLAPPED,*PMYOVERLAPPED;

DWORD WINAPI ThreadProc(LPVOID lParam) {
    PMYOVERLAPPED pol = (PMYOVERLAPPED)lParam;
    WaitForSingleObject(pol->hFile, INFINITE);
    for (int i=0;i<10;i++)
    {
        printf("%d:%d \n", pol->nIndex,pol->pBuf[i]);
    }
    printf("讀完了!\n");
    return 0;
}

int main()
{
    // 1. 異步IO標記
    // 有了這個標記 該文件就變為可等待的內核對象
    // 後面的read write函數就變為非阻塞的
    HANDLE hFile = CreateFile(L"123.txt", GENERIC_READ,
        FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL| FILE_FLAG_OVERLAPPED, NULL);
    // 2. 文件讀取
    PMYOVERLAPPED pol = new MYOVERLAPPED{};
    pol->ol.Offset = 0x0;// 從偏移0x100這個位置開始讀
    // pol->hEvent == NULL; 系統讀取完成後,會把我的hFile變為有信號狀態
    pol->hFile = hFile;
    pol->pBuf = new BYTE[0x1000]{};
    pol->nIndex =1;
    ReadFile(hFile,
        pol->pBuf,
        0x1000,
        NULL,//實際讀取的個數,由OVERLAPPED結構體指定
        (LPOVERLAPPED)pol);
    HANDLE hThread = CreateThread(NULL, NULL, ThreadProc, pol, NULL, NULL);

    PMYOVERLAPPED pol2 = new MYOVERLAPPED{};
    pol2->ol.Offset = 0x0;// 從偏移0x100這個位置開始讀
                           // pol->hEvent == NULL; 系統讀取完成後,會把我的hFile變為有信號狀態
    pol2->hFile = hFile;
    pol2->pBuf = new BYTE[0x1000]{};
    pol2->nIndex = 2;
    ReadFile(hFile,
        pol2->pBuf,
        0x1000,
        NULL,//實際讀取的個數,由OVERLAPPED結構體指定
        (LPOVERLAPPED)pol2);
    HANDLE hThread2 = CreateThread(NULL, NULL, ThreadProc, pol2, NULL, NULL);

    // ......幹其他事
    WaitForSingleObject(hThread, INFINITE);
    WaitForSingleObject(hThread2, INFINITE);
    return 0;
}

2.等待事件對象

#include "stdafx.h"
#include <windows.h>
typedef struct _MYOVERLAPPED {
    OVERLAPPED ol;
    HANDLE hFile;
    PBYTE pBuf;
    int nIndex;
}MYOVERLAPPED, *PMYOVERLAPPED;

DWORD WINAPI ThreadProc(LPVOID lParam) {
    PMYOVERLAPPED pol = (PMYOVERLAPPED)lParam;
    printf("開始等待......\n");
    WaitForSingleObject(pol->ol.hEvent, INFINITE);
    for (int i = 0; i < 10; i++)
    {
        printf("%d:%02x \n", pol->nIndex, pol->pBuf[i]);
    }
    printf("讀完了!\n");
    return 0;
}

int main()
{
    // 1. 異步IO標記
    // 有了這個標記 該文件就變為可等待的內核對象
    // 後面的read write函數就變為非阻塞的
    HANDLE hFile = CreateFile(L"..\\Debug\\123.exe", GENERIC_READ,
        FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
    // 2. 文件讀取
    PMYOVERLAPPED pol = new MYOVERLAPPED{};
    pol->ol.Offset = 0x100;// 從偏移0x100這個位置開始讀
    pol->ol.hEvent = CreateEvent(NULL,NULL,FALSE,NULL); //系統讀取完成後,會把我的hFile變為有信號狀態
    pol->hFile = hFile;// 被無視
    pol->pBuf = new BYTE[0x1000]{};
    pol->nIndex = 1;
    ReadFile(hFile,
        pol->pBuf,
        0x1000,
        NULL,//實際讀取的個數,由OVERLAPPED結構體指定
        (LPOVERLAPPED)pol);
    HANDLE hThread = CreateThread(NULL, NULL, ThreadProc, pol, NULL, NULL);

    PMYOVERLAPPED pol2 = new MYOVERLAPPED{};
    pol2->ol.Offset = 0x200;// 從偏移0x100這個位置開始讀
    pol2->ol.hEvent = CreateEvent(NULL, NULL, FALSE, NULL); //系統讀取完成後,會把我的hFile變為有信號狀態
    pol2->hFile = hFile;// 被無視
    pol2->pBuf = new BYTE[0x1000]{};
    pol2->nIndex = 2;
    ReadFile(hFile,
        pol2->pBuf,
        0x1000,
        NULL,//實際讀取的個數,由OVERLAPPED結構體指定
        (LPOVERLAPPED)pol2);
    HANDLE hThread2 = CreateThread(NULL, NULL, ThreadProc, pol2, NULL, NULL);

    // ......幹其他事
    WaitForSingleObject(hThread, INFINITE);
    WaitForSingleObject(hThread2, INFINITE);
    return 0;
}

3.異步調用(APC調用)

異步調用提供了這樣一個機制:你可以在讀取文件或者寫入文件的時候,提供一個回調函數,當讀寫任務完成時,就會調用這個回調函數。

#include "stdafx.h"
#include <windows.h>
typedef struct _MYOVERLAPPED {
    OVERLAPPED ol;
    HANDLE hFile;
    PBYTE pBuf;
    int nIndex;
}MYOVERLAPPED, *PMYOVERLAPPED;

// 提交任務的線程處理,其他線程看著
VOID CALLBACK FileIOCompletionRoutine(
    _In_    DWORD        dwErrorCode,
    _In_    DWORD        dwNumberOfBytesTransfered,
    _Inout_ LPOVERLAPPED lpOverlapped
) {
    PMYOVERLAPPED pol = (PMYOVERLAPPED)lpOverlapped;
    for (int i = 0; i < 10; i++)
    {
        printf("%d:%02x \n", pol->nIndex, pol->pBuf[i]);
    }
    printf("讀完了!\n");
}

int main()
{
    // 1. 異步IO標記
    // 有了這個標記 該文件就變為可等待的內核對象
    // 後面的read write函數就變為非阻塞的
    HANDLE hFile = CreateFile(L"..\\Debug\\123.exe", GENERIC_READ,
        FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
    // 2. 文件讀取
    PMYOVERLAPPED pol = new MYOVERLAPPED{};
    pol->ol.Offset = 0x100;// 從偏移0x100這個位置開始讀
    // hEvent被無視   hFile也被無視
    //pol->ol.hEvent = CreateEvent(NULL, NULL, FALSE, NULL); //系統讀取完成後,會把我的hFile變為有信號狀態
    pol->hFile = hFile;// 被無視
    pol->pBuf = new BYTE[0x1000]{};
    pol->nIndex = 1;
    ReadFileEx(hFile,
        pol->pBuf,
        0x1000,
        (LPOVERLAPPED)pol,
        FileIOCompletionRoutine);// 完成後直接調用該回調函數,不用等待文件句柄/事件對象

    PMYOVERLAPPED pol2 = new MYOVERLAPPED{};
    pol2->ol.Offset = 0x200;// 從偏移0x100這個位置開始讀
    //pol2->ol.hEvent = CreateEvent(NULL, NULL, FALSE, NULL); //系統讀取完成後,會把我的hFile變為有信號狀態
    //pol2->hFile = hFile;// 被無視
    pol2->pBuf = new BYTE[0x1000]{};
    pol2->nIndex = 2;
    ReadFileEx(hFile,
        pol2->pBuf,
        0x1000,
        (LPOVERLAPPED)pol2,
        FileIOCompletionRoutine);
    // FileIOCompletionRoutine有系統調用
    // 哪個線程執行該函數呢
    // 哪個線程read/write 哪個線程執行
    // ......幹其他事

    // 忙完了  想起來還有兩個函數等著我呢
    // CPU檢測到當前線程的APC隊列裏有函數需要執行
    // 就去執行該函數,執行完返回
    // 只有當第2個參數是TRUE才去執行
    SleepEx(200, TRUE);
//  WaitForSingleObjectEx()
    return 0;
}

Windows異步I/O詳解