异步IO、APC、IO完成端口、线程池与高性能服务器 (三)

异步IO、APC、IO完成端口、线程池与高性能服务器 (三)  作者 Fang(fangguicheng@21cn.com)

关键字 异步IO APC IO完成端口 线程池 高性能 可伸缩 服务器
原作者姓名 Fang(fangguicheng@21cn.com)

正文
异步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 设备驱动程序设计指南》

上一篇:Java并发——线程同步Volatile与Synchronized详解


下一篇:《超越需求:敏捷思维模式下的分析》—第1章 1.9节总结