如何在`CreateIoCompletionPort`

时间:2016-06-30 21:53:32

标签: c++ c windows multithreading winapi

查看CreateIoCompletionPort IoCompletionPort,我们读到了:

  

NumberOfConcurrentThreads [in]

     
    

操作系统可以允许同时处理I / O完成端口的I / O完成数据包的最大线程数。如果ExistingCompletionPort参数不为NULL,则忽略此参数。

         

如果此参数为零,则系统允许与系统中的处理器一样多的并发运行线程。

  

但是,我找到的文档并未表明0实际创建了任何线程。

实际上,Microsoft提供的MSDN documentation使用以下代码来确定可用的处理内核数量(而不是将NumberOfConcurrentThreads传递给// The general value of the thread count is the system's processor count. SYSTEM_INFO sysInfo = { 0 }; GetNativeSystemInfo(&sysInfo); const DWORD dwThreadCount = sysInfo.dwNumberOfProcessors; // A class in the example that wraps around IoCompletionPort IOCompletionPort port; // Construct the thread pool HANDLE* hThreads = new HANDLE[dwThreadCount]; for (DWORD i = 0; i < dwThreadCount; ++i) { // The threads run CompletionThread hThreads[i] = CreateThread(0, 0, IOCompletionThread, &port, 0, NULL); } )然后实际创建了那么多线程

IoCompletionPort

在我看来,这表明某种方式存在“承载能力”#34;与new HANDLE[++dwThreadCount]相关联。但这是如何体现的呢?我很难看到如何(或者甚至为什么会这样做)一个可以访问完成端口的线程可以防止从完成端口出列。

实际上,我尝试将创建线程的行修改为const(并从声明中删除NumberOfConcurrentThreads说明符),并且示例似乎没有抱怨而执行。我刚刚在执行结束时发现了一个额外的超时错误。

我目前唯一的结论就是说ntpd -d -q -n -p pool.ntp.org 是一个&#34;虚拟&#34;变量没有实际用途,所以我错过了什么?

2 个答案:

答案 0 :(得分:3)

I / O完成端口本身不会创建线程。 NumberOfConcurrentThreads参数指定允许多少线程同时并行处理完成数据包。这在另一个MSDN页面上有更详细的解释:

I/O Completion Ports

  

I / O完成端口的工作方式

     

...

     

虽然任意数量的线程可以为指定的I / O完成端口调用GetQueuedCompletionStatus,但是当指定的线程第一次调用GetQueuedCompletionStatus时,它将与指定的I / O完成端口关联,直到发生以下三种情况之一:线程退出,指定不同的I / O完成端口,或关闭I / O完成端口。换句话说,单个线程最多可以与一个I / O完成端口相关联。

     

当完成数据包排队到I / O完成端口时,系统首先检查与该端口关联的线程数正在运行。 如果运行的线程数小于并发值(在下一节中讨论),则允许其中一个等待线程(最新的线程)处理完成数据包。当正在运行的线程完成其处理时,它通常再次调用GetQueuedCompletionStatus,此时它将返回下一个完成数据包,或者等待队列为空。

     

...

     

线程和并发

     

要仔细考虑的I / O完成端口最重要的属性是并发值。 通过CreateIoCompletionPort参数使用NumberOfConcurrentThreads创建完成端口的并发值时指定。此值限制与完成端口关联的可运行线程的数量。当与完成端口关联的可运行线程的总数达到并发值时,系统会阻止与该完成端口关联的任何后续线程的执行,直到可运行线程的数量降至并发值以下。

     

当队列中有完成数据包等待时,会出现最有效的情况,但由于端口已达到其并发限制,因此无法满足等待。考虑在GetQueuedCompletionStatus函数调用中等待的一个和多个线程的并发值会发生什么。在这种情况下,如果队列总是有完成数据包等待,当正在运行的线程调用GetQueuedCompletionStatus时,它将不会阻止执行,因为如前所述,线程队列是LIFO。相反,该线程将立即获取下一个排队的完成数据包。不会发生线程上下文切换,因为正在运行的线程不断地拾取完成数据包而其他线程无法运行。

     

...

     

要为并发值选择的最佳总体最大值是计算机上的CPU数。如果您的事务需要冗长的计算,则更大的并发值将允许更多线程运行。每个完成包可能需要更长的时间才能完成,但是将同时处理更多的完成包。您可以结合分析工具试验并发值,以便为您的应用程序实现最佳效果。

     

如果另一个与同一I / O完成端口关联的正在运行的线程因其他原因进入等待状态,系统还允许在GetQueuedCompletionStatus中等待的线程处理完成数据包,例如SuspendThread功能。当处于等待状态的线程再次开始运行时,可能会有一段短暂的时间,当活动线程数超过并发值时。但是,系统通过在活动线程数低于并发值之前不允许任何新的活动线程来快速减少此数量。这是让您的应用程序在其线程池中创建比并发值更多线程的一个原因。线程池管理超出了本主题的范围,但一个好的经验法则是在线程池中至少有两倍于系统上的处理器的线程。有关线程池的其他信息,请参阅Thread Pools

答案 1 :(得分:2)

IOCP基于KQUEUE object

struct KQUEUE {
    DISPATCHER_HEADER Header;
    LIST_ENTRY EntryListHead;
    ULONG CurrentCount;
    ULONG MaximumCount;
    LIST_ENTRY ThreadListHead;
};

MaximumCountKeNumberProcessors分配,如果KeInitializeQueue(使用Count == 0)或NumberOfConcurrentThreads初始化,则从CreateIoCompletionPort(ZwCreateIoCompletion)初始化。

CurrentCount是绑定到此KQUEUE的“活动”(未等待)线程的数量(在ETHREAD结构中有一个特殊字段:KQUEUE* Queue

如果线程尝试通过调用KQUEUEKeRemoveQueueZwRemoveIoCompletion)从GetQueuedCompletionStatus删除数据包,并且IOCP中没有数据包(EntryListHead是空的),当然线程将进入等待状态。

但如果存在数据包,系统会查看CurrentCountMaximumCount。如果CurrentCount < MaximumCount,将删除数据包(并CurrentCount++递增)。否则,线程将进入等待状态。

如果一个线程将一个新数据包插入到其他线程先前正在等待的IOCP上,那么只有当(CurrentCount < MaximumCount)时才会唤醒一个(以LIFO顺序)。

当:

  • 线程开始等待某个对象(KeWaitForObject
  • 线程被暂停(这也是内部调用KeWaitForObject
  • KeDelayExecution(睡眠)被称为

系统查看Thread->Queue,如果不是0,则CurrentCount--会递减。此外,如果IOCP中存在数据包,则线程正在等待它,然后CurrentCount < MaximumCount将唤醒一个线程。

所以逻辑实际上非常复杂,但主要的不是MaximumCount个线程能够同时处理来自IOCP的数据包。

通常,最好的值是KeNumberProcessors,但是(在某些特殊情况下)分析工具可以帮助您确定更适合您情况的不同值。