参数“NumberOfConcurrentThreads”如何在“CreateIoCompletionPort”中使用
How the parameter `NumberOfConcurrentThreads` is used in `CreateIoCompletionPort`
查看CreateIoCompletionPort
的MSDN文档:
NumberOfConcurrentThreads[在]
操作系统允许并发处理I/O完成端口的I/O完成数据包的最大线程数。如果ExistingCompletionPort参数不为NULL,则忽略此参数。
如果该参数为零,系统允许并发运行的线程数与系统中处理器的数量一样多。
然而,我找到的文档没有指出IoCompletionPort
实际上创建任何线程。
实际上,Microsoft提供的示例使用以下代码来确定有多少处理内核可用(而不是将0
传递给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
相关的"承载能力"。但这是如何体现的呢?我很难理解如何(甚至为什么这是可取的)一个访问完成端口的线程将被阻止从完成端口脱离队列。
实际上,我尝试修改创建线程为new HANDLE[++dwThreadCount]
的行(并从声明中删除const
说明符),并且该示例似乎没有任何问题。我刚注意到在执行结束时有一个额外的超时错误。
我目前唯一的结论是说NumberOfConcurrentThreads
是一个没有实际用途的"虚拟"变量,所以我错过了什么?
I/O Completion Port本身不创建线程。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完成端口最重要的属性是并发值。完成端口的并发值是通过
NumberOfConcurrentThreads
参数与CreateIoCompletionPort
一起创建完成端口时指定的。此值限制与完成端口关联的可运行线程的数量。当与该完成端口关联的可运行线程总数达到该并发值时,系统将阻塞与该完成端口关联的任何后续线程的执行,直到可运行线程数低于该并发值。最有效的场景发生在队列中有完成数据包等待,但由于端口已达到并发限制,因此无法满足等待。考虑一个并发值和多个线程在
GetQueuedCompletionStatus
函数调用中等待时会发生什么。在这种情况下,如果队列总是有完成包等待,当正在运行的线程调用GetQueuedCompletionStatus
时,它不会阻塞执行,因为正如前面提到的,线程队列是后进先出的。相反,这个线程将立即获取下一个排队的完成包。不会发生线程上下文切换,因为正在运行的线程不断地拾取完成包,而其他线程无法运行。…
选择并发值的最佳最大值是计算机上的cpu数量。如果您的事务需要长时间的计算,那么较大的并发性值将允许更多的线程运行。每个完井包可能需要更长的时间来完成,但同时会处理更多的完井包。您可以将并发性值与分析工具结合起来进行实验,以实现应用程序的最佳效果。
如果与相同I/O完成端口相关联的另一个正在运行的线程由于其他原因(例如
SuspendThread
函数)进入等待状态,则系统还允许在GetQueuedCompletionStatus
中等待的线程处理完成数据包。当处于等待状态的线程再次开始运行时,可能会有一段时间活动线程的数量超过并发值。但是,系统通过不允许任何新的活动线程,直到活动线程的数量低于并发值,从而迅速减少这个数字。这是让应用程序在线程池中创建的线程多于并发值的原因之一。线程池管理超出了本主题的范围,但是一个好的经验法则是,线程池中的线程数量至少是系统上处理器数量的两倍。有关线程池的其他信息,请参见线程池。
IOCP基于KQUEUE
对象:
struct KQUEUE {
DISPATCHER_HEADER Header;
LIST_ENTRY EntryListHead;
ULONG CurrentCount;
ULONG MaximumCount;
LIST_ENTRY ThreadListHead;
};
MaximumCount
如果被KeInitializeQueue
(Count == 0)初始化,则由KeNumberProcessors
赋值;如果从CreateIoCompletionPort (ZwCreateIoCompletion)初始化,则由NumberOfConcurrentThreads
赋值。
CurrentCount
是绑定到KQUEUE
(在ETHREAD
结构中有一个特殊的字段:KQUEUE* Queue
)的"活动"(未等待)线程的数量。
如果线程试图通过调用KeRemoveQueue
或ZwRemoveIoCompletion
(GetQueuedCompletionStatus
)从KQUEUE
中删除数据包,并且IOCP中没有数据包(EntryListHead
为空),那么线程当然会进入等待状态。
但是如果一个数据包存在,系统会查看CurrentCount
和MaximumCount
。如果是CurrentCount < MaximumCount
,将删除一个数据包(CurrentCount++
递增)。否则,线程将进入等待状态。
如果一个线程向IOCP插入一个新的数据包,而其他线程之前正在等待,只有当(CurrentCount < MaximumCount
)时,一个线程才会被唤醒(以后进先出顺序)。
- 一个线程开始等待某个对象(通过
KeWaitForObject
) - 线程被挂起(这也是内部调用
KeWaitForObject
) -
KeDelayExecution
(Sleep)被称为
系统查看Thread->Queue
,如果不是0
,则CurrentCount--
递减。另外,如果IOCP中存在数据包,线程正在等待它,CurrentCount < MaximumCount
则一个线程将被唤醒。
所以逻辑实际上是相当复杂的,但主要的一点是不超过MaximumCount
线程将能够处理来自IOCP的数据包。
通常,这个值的最佳值是KeNumberProcessors
,但是(在某些特殊情况下)分析工具可以帮助您决定一个更适合您的情况的不同值。
- 没有找到相关文章