異步IO、APC、IO完成端口、線程池與高性能服務器 (三)

轉載: http://www.vchelp.net/
原作者姓名 Fang([email protected])

正文
異步IO、APC、IO完成端口、線程池與高性能服務器之三 IO完成端口

IO完成端口

下面摘抄於MSDN《I/O Completion Ports》,smallfool翻譯,原文請參考CSDN文檔中心文章《I/O Completion Ports》, http://dev.csdn.net/Develop/article/29%5C29240.shtm
I/O完成端口是一種機制,通過這個機制,應用程序在啓動時會首先創建一個線程池,然後該應用程序使用線程池處理異步I/O請求。這些線程被創建的唯一目的就是用於處理I/O請求。對於處理大量併發異步I/O請求的應用程序來說,相比於在I/O請求發生時創建線程來說,使用完成端口(s)它就可以做的更快且更有效率。
CreateIoCompletionPort函數會使一個I/O完成端口與一個或多個文件句柄發生關聯。當與一個完成端口相關的文件句柄上啓動的異步I/O操作完成時,一個I/O完成包就會進入到該完成端口的隊列中。對於多個文件句柄來說,這種機制可以用來把多文件句柄的同步點放在單個對象中。(言下之意,如果我們需要對每個句柄文件進行同步,一般而言我們需要多個對象(如:Event來同步),而我們使用IO Complete Port 來實現異步操作,我們可以同多個文件相關聯,每當一個文件中的異步操作完成,就會把一個complete package放到隊列中,這樣我們就可以使用這個來完成所有文件句柄的同步)
調用GetQueuedCompletionStatus函數,某個線程就會等待一個完成包進入到完成端口的隊列中,而不是直接等待異步I/O請求完成。線程(們)就會阻塞於它們的運行在完成端口(按照後進先出隊列順序的被釋放)。這就意味着當一個完成包進入到完成端口的隊列中時,系統會釋放最近被阻塞在該完成端口的線程。
調用GetQueuedCompletionStatus,線程就會將會與某個指定的完成端口建立聯繫,一直延續其該線程的存在週期,或被指定了不同的完成端口,或者釋放了與完成端口的聯繫。一個線程只能與最多不超過一個的完成端口發生聯繫。
完成端口最重要的特性就是併發量。完成端口的併發量可以在創建該完成端口時指定。該併發量限制了與該完成端口相關聯的可運行線程的數目。當與該完成端口相關聯的可運行線程的總數目達到了該併發量,系統就會阻塞任何與該完成端口相關聯的後續線程的執行,直到與該完成端口相關聯的可運行線程數目下降到小於該併發量爲止。最有效的假想是發生在有完成包在隊列中等待,而沒有等待被滿足,因爲此時完成端口達到了其併發量的極限。此時,一個正在運行中的線程調用GetQueuedCompletionStatus時,它就會立刻從隊列中取走該完成包。這樣就不存在着環境的切換,因爲該處於運行中的線程就會連續不斷地從隊列中取走完成包,而其他的線程就不能運行了。
對於併發量最好的挑選值就是您計算機中CPU的數目。如果您的事務處理需要一個漫長的計算時間,一個比較大的併發量可以允許更多線程來運行。雖然完成每個事務處理需要花費更長的時間,但更多的事務可以同時被處理。對於應用程序來說,很容易通過測試併發量來獲得最好的效果。
PostQueuedCompletionStatus函數允許應用程序可以針對自定義的專用I/O完成包進行排隊,而無需啓動一個異步I/O操作。這點對於通知外部事件的工作者線程來說很有用。
在沒有更多的引用針對某個完成端口時,需要釋放該完成端口。該完成端口句柄以及與該完成端口相關聯的所有文件句柄都需要被釋放。調用CloseHandle可以釋放完成端口的句柄。

下面的代碼利用IO完成端口做了一個簡單的線程池。

/************************************************************************/
/* Test IOCompletePort.                                                 */
/************************************************************************/

DWORD WINAPI IOCPWorkThread(PVOID pParam)
{
    HANDLE CompletePort = (HANDLE)pParam;
    PVOID UserParam;
    WORK_ITEM_PROC UserProc;
    LPOVERLAPPED pOverlapped;
    
    for(;;)
    {
        BOOL bRet = GetQueuedCompletionStatus(
            CompletePort,
            (LPDWORD)&UserParam,
            (LPDWORD)&UserProc,
            &pOverlapped,
            INFINITE);

        _ASSERT(bRet);

        if(UserProc == NULL) // Quit signal.
            break;

        // execute user's proc.        
        UserProc(UserParam);        
    }

    return 0;
}

void TestIOCompletePort(BOOL bWaitMode, LONG ThreadNum)
{
    HANDLE CompletePort;
    OVERLAPPED Overlapped = {0, 0, 0, 0, NULL};

    CompletePort = CreateIoCompletionPort(
        INVALID_HANDLE_VALUE,
        NULL,
        NULL,
        0);
    
    // Create threads.
    for(int i=0; i<ThreadNum; i++)
    {
        HANDLE hThread = CreateThread(NULL,
            0,
            IOCPWorkThread,
            CompletePort,
            0,
            NULL);

        CloseHandle(hThread);
    }


    CompleteEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    BeginTime = GetTickCount();
    ItemCount = 20;

    for(i=0; i<20; i++)
    {
        PostQueuedCompletionStatus(
            CompletePort,
            (DWORD)bWaitMode,
            (DWORD)UserProc1,
            &Overlapped);
    }
    
    WaitForSingleObject(CompleteEvent, INFINITE);
    CloseHandle(CompleteEvent);


    // Destroy all threads.
    for(i=0; i<ThreadNum; i++)
    {
        PostQueuedCompletionStatus(
            CompletePort,
            NULL,
            NULL,
            &Overlapped);
    }

    Sleep(1000); // wait all thread exit.

    CloseHandle(CompletePort);
}


參考書目

1,    MSDN Library
2,    《Windows高級編程指南》
3,    《Windows核心編程》
4,    《Windows 2000 設備驅動程序設計指南》
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章