標籤: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詳解