Windows非同步IO (Asynchronous IO) (二)

來源:互聯網
上載者:User

        前一篇文章我們知道如何向裝置驅動發送非同步IO請求。顯然,僅僅知道這些肯定是不夠的,使用者線程必須在必要的時候收到裝置驅動的完成通知(Completion Notification),以執行相關任務,不然非同步IO沒有任何意義。Windows提供四種方法來接受來自裝置驅動的完成通知。

        也許有朋友已經想到了一個方法。前一篇提到,我們可以通過Overlapped的Internal成員判斷IO請求的狀態,所以我們可以實現一個busy loop來檢查Internal的值是否為STATUS_PENDING,這不就行了嗎?理論上來說,當然是可以的,不過這似乎沒有什麼實用性。眾所周知,busy loop太浪費CPU了!好不容易從慢速裝置上省下來的CPU時間怎麼能輕易用在空迴圈上?!事實上,Windows為我們提供了四種方法來接受裝置驅動的完成通知,這裡先介紹前三種。

        1,觸發裝置核心對象(Singaling a Device Kernel Object)

        在ReadFile/WriteFile函數將IO請求加入請求隊列前,函數會將裝置核心對象設定為未觸發狀態(Nonsignaled)。當裝置驅動完成IO請求後,驅動會將裝置核心對象設為觸發狀態(Signaled)。範例程式碼:

        #include <windows.h><br />#include <stdio.h></p><p>int main()<br />{<br />HANDLEhFile = NULL;<br />BYTEbuff[1024];<br />OVERLAPPEDol = {0};<br />BOOLbRet = FALSE;<br />DWORDdwCode = 0;</p><p>hFile = CreateFile(TEXT("data.txt"), GENERIC_READ, FILE_SHARE_READ,<br /> 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);</p><p>ol.Offset = 16; //Read data from file starting at byte 16<br />bRet = ReadFile(hFile, buff, 1024, NULL, &ol);</p><p>if (bRet) //IO request is completed immediately<br />{</p><p>}<br />else if ((dwCode = GetLastError()) == ERROR_IO_PENDING) //IO request is queued successfully<br />{<br />WaitForSingleObject(hFile, INFINITE); // The IO is being performed asynchronously; wait for it to complete<br />}<br />else<br />{<br />//Error Handling here<br />}</p><p>return EXIT_SUCCESS;<br />}

        上面的代碼和前一篇的代碼幾乎相同,只是在第二個if語句中以hFile作為參數調用WaitForSignalObject函數。此調用會將使用者線程掛起直到hFile核心對象變成觸發狀態。裝置驅動在完成IO請求後就會觸發hFile核心對象,使WaitForSignalObject函數返回,這樣使用者線程就能執行相應的任務。到這裡,好像問題解決了,不過細心的朋友可能發現,此方法看似有效,事實上沒有多大用處。因為它不能處理使用者線程同時發送多個IO請求的情況。裝置驅動在完成任何一個IO請求後,都會觸發裝置核心對象,導致WaitForSignalObejct函數返回,但使用者線程沒有得到任何有用的資訊區分是哪個IO請求完成了。:)

        2,觸發事件核心對象(Signaling an Event Kernel Object)

        現在我們知道了第一種方法沒有什麼實用性,不過有朋友可能又想到瞭解決方法!前一篇說過的,每一個IO請求都有一個對應的Overlapped結構,每一個Overlapped又都有一個hEvent成員,也就是說每一個IO請求都對應一個事件,這不就解決了麼!範例程式碼:

        #include <windows.h><br />#include <stdio.h></p><p>int main() {<br />HANDLEhFile = NULL;<br />BYTEbReadBuff[1024];<br />BYTEbWriteBuff[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};<br />OVERLAPPEDolRead = {0};<br />OVERLAPPEDolWrite = {0};<br />HANDLEhEvents[2] = {0};<br />DWORDdwWaitRet = 0;</p><p>hFile = CreateFile(TEXT("data.txt"), GENERIC_READ, FILE_SHARE_READ,<br /> 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);</p><p>olRead.Offset = 16; //Read data from file starting at byte 16<br />olRead.hEvent = CreateEvent(NULL,TRUE, FALSE, NULL);<br />ReadFile(hFile, bReadBuff, 1024, NULL, &olRead);</p><p>olWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);<br />WriteFile(hFile, bWriteBuff, 16, NULL, &olWrite);</p><p>hEvents[0] = olRead.hEvent;<br />hEvents[1] = olWrite.hEvent;<br />dwWaitRet = WaitForMultipleObjects(2, hEvents, FALSE, INFINITE); //Wait multiple IO requests<br />switch(dwWaitRet - WAIT_OBJECT_0)<br />{<br />case 0: //Read completed<br />break;<br />case 1:<br />break; //Write completed<br />}<br />return EXIT_SUCCESS;<br />}

        在發送一個IO請求前,使用者線程給每一個Overlapped結構中的hEvent成員指定一個Event核心對象。裝置驅動在完成一個IO請求後,會檢查其Overlapped結構中的hEvent成員是否為空白,如果不為空白,則用SetEvent函數將其設定為觸發狀態。然後,使用者線程用WaitForMultipleObjects函數等待前面的事件對象數組。在WaitForMultipleObjects函數返回後,使用者線程可以通過傳回值區分是哪個IO請求完成。怎麼樣,比第一種方法好用很多吧。:)

        但它有一個明顯的缺點,就是WaitForMultipleObjects一次能等待的最大HANDLE數不超過MAXIMUM_WAIT_OBJECTS,此值為64。不過也是有辦法通過WaitForMultipleObjects等待超過64的HANDLE,這裡就不介紹了。

        3,可提醒IO(Alertable IO)

        到這裡,除了第一篇的內容,前面的兩種方法都可以暫時忘記了。因為這種方法跟前面的兩種方法沒有任何相似性。

        Windows在建立一個線程時,同時會給每個線程建立一個隊列,叫做非同步程序呼叫隊列(Asynchronous Procedure Call, APC)。為了使用這個特性,我們應該用ReadFileEx/WriteFileEx函數替換原來的ReadFile/WriteFile函數。

        BOOL ReadFileEx(<br />HANDLE hFile,<br />PVOID pvBuffer,<br />DWORD nNumBytesToRead,<br />OVERLAPPED* pOverlapped,<br />LPOVERLAPPED_COMPLETION_ROUTINE pfnCompletionRoutine);</p><p>BOOL WriteFileEx(<br /> HANDLE hFile,<br /> CONST VOID *pvBuffer,<br /> DWORD nNumBytesToWrite,<br /> OVERLAPPED* pOverlapped,<br /> LPOVERLAPPED_COMPLETION_ROUTINE pfnCompletionRoutine);

        這兩個方法與原來方法有兩處不同。首先,*Ex沒有一個指向DWORD的指標用來返回以傳輸的位元組數。其次,*Ex函數需要指定一個回呼函數地址,這個回呼函數稱為完成函數(Completion Routine)。

        VOID WINAPI CompletionRoutine(<br /> DWORD dwError,<br /> DWORD dwNumBytes,<br /> OVERLAPPED* po);

        當使用者線程用*Ex函數發送一個IO請求時,函數會將完成函數的地址傳給裝置驅動。當裝置驅動完成一個IO請求時,會在發出此IO請求的線程的APC隊列中添加一項,此項包括完成函數地址和Overlapped結構的地址。當線程置於可提醒狀態時,系統會檢查它的APC隊列,對隊列中的每一項,系統會調用完成函數,並傳入IO錯誤碼,以傳輸的位元組數,以及Overlapped結構地址。也就是說,可提醒IO使使用者線程能在每一個IO請求被完成後,都能通過調用對應的完成函數來執行相關任務。當然,你不能期望執行完成函數的次序與發送IO請求的次序一致。

        現在出現了新問題,什麼是線程的可提醒狀態。可提醒狀態是線程可以被安全中斷的狀態,Windows提供了六個函數使線程置於可提醒狀態。

        DWORD SleepEx(<br /> DWORD dwMilliseconds,<br /> BOOL bAlertable);</p><p>DWORD WaitForSingleObjectEx(<br /> HANDLE hObject,<br /> DWORD dwMilliseconds,<br /> BOOL bAlertable);</p><p>DWORD WaitForMultipleObjectsEx(<br /> DWORD cObjects,<br /> CONST HANDLE* phObjects,<br /> BOOL bWaitAll,<br /> DWORD dwMilliseconds,<br /> BOOL bAlertable);</p><p>BOOL SignalObjectAndWait(<br /> HANDLE hObjectToSignal,<br /> HANDLE hObjectToWaitOn,<br /> DWORD dwMilliseconds,<br /> BOOL bAlertable);</p><p>BOOL GetQueuedCompletionStatusEx(<br /> HANDLE hCompPort,<br /> LPOVERLAPPED_ENTRY pCompPortEntries,<br /> ULONG ulCount,<br /> PULONG pulNumEntriesRemoved,<br /> DWORD dwMilliseconds,<br /> BOOL bAlertable);</p><p>DWORD MsgWaitForMultipleObjectsEx(<br /> DWORD nCount,<br /> CONST HANDLE* pHandles,<br /> DWORD dwMilliseconds,<br /> DWORD dwWakeMask,<br /> DWORD dwFlags);</p><p>

        這六個函數最後一個參數都是BOOL類型,表示調用線程是否應該把自己置於可提醒狀態。事實上,這些*Ex函數對應的非擴充版本在內部都調用對應的*Ex函數,當然bAlertable參數被設為FALSE。

        到這裡,可提醒IO的概貌就浮現了。不過,我們不提倡使用可提醒IO,因為它太麻煩。:)

        1)使用者線程必須為每一個IO請求實現一個完成函數,過多的IO請求當然會導致代碼臃腫。如果共用完成函數,一個函數又難以提供足夠的資訊區分不同的情況,總之,麻煩。

        2)發出IO請求的線程必須處理這些請求的完成函數,如果一個線程發出過多的IO請求,該線程也必須任勞任怨的處理每一個完成函數,即使系統中有其它閒置線程。

        不錯,下一篇介紹的方法(IOCP)會很好的解決這兩個問題。

       

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.