CreateIoCompletionPort

时间:2018-03-06 19:14:50

标签: network-programming winsock winsock2 iocp

仍然NumberOfConcurrentThreads中的CreateIoCompletionPort()参数感到困惑。我已阅读并重新阅读MSDN dox,但引用

  

此值限制与之关联的可运行线程的数量   完成港口。

仍然困扰着我。

问题

我们假设我将此值指定为4.在这种情况下,这是否意味着:

1)一个线程可以调用GetQueuedCompletionStatus()(此时我可以允许另外3个线程进行此调用),然后一旦该调用返回(即我们有一个完成包),我就可以拥有4个线程再次调用此函数,

2)一个线程可以调用GetQueuedCompletionStatus()(此时我可以允许另外3个线程进行此调用),然后一旦该调用返回(即我们有一个完成包),我继续处理该数据包。只有当我完成处理数据包后,我才会调用GetQueuedCompletionStatus(),此时我可以然后让4个线程再次调用此函数。

看到我的困惑?使用短语' runnable threads'。

我认为可能是后者,因为上面的链接也引用了

  

如果您的交易需要长时间的计算,则更大   并发值将允许更多线程运行。每次完成   数据包可能需要更长的时间才能完成,但会有更多的完成数据包   同时处理。

这最终会影响我们设计服务器的方式。考虑从客户端接收数据的服务器,然后将该数据回显给日志记录服务器。这是我们的线程例程的样子:

DWORD WINAPI ServerWorkerThread(HANDLE hCompletionPort)
{
    DWORD BytesTransferred;
    CPerHandleData* PerHandleData = nullptr;
    CPerOperationData* PerIoData = nullptr;

    while (TRUE)
    {
        if (GetQueuedCompletionStatus(hCompletionPort, &BytesTransferred,
            (PULONG_PTR)&PerHandleData, (LPOVERLAPPED*)&PerIoData, INFINITE))
        {
            // OK, we have 'BytesTransferred' of data in 'PerIoData', process it:
            // send the data onto our logging servers, then loop back around
            send(...);
        }
    }
    return 0;
}

现在假设我有一台四核机器;如果我在NumberOfConcurrentThreads的调用中将CreateIoCompletionPort()保留为零,我将有四个线程运行ServerWorkerThread()。细

我担心的是,由于网络流量,send()电话可能需要很长时间。因此,我可能会从无法出列的客户端接收大量数据,因为所有四个线程都需要很长时间才能发送数据?!

我是否错过了这一点?

更新07.03.2018 (现已解决:见this comment。)

我的机器上运行了8个线程,每个线程运行ServerWorkerThread()

DWORD WINAPI ServerWorkerThread(HANDLE hCompletionPort)
{
DWORD BytesTransferred;
CPerHandleData* PerHandleData = nullptr;
CPerOperationData* PerIoData = nullptr;

while (TRUE)
{
    if (GetQueuedCompletionStatus(hCompletionPort, &BytesTransferred,
        (PULONG_PTR)&PerHandleData, (LPOVERLAPPED*)&PerIoData, INFINITE))
    {
    switch (PerIoData->Operation)
    {
    case  CPerOperationData::ACCEPT_COMPLETED:
    {
        // This case is fired when a new connection is made
        while (1) {}
    }
}
}

我只有一个未完成的AcceptEx()电话;当它被新连接填满时我会发布另一个连接。我不等待AcceptEx()收到数据。

我按如下方式创建完成端口:

CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 4)

现在,因为我只在完成端口允许4个线程,我认为因为我保持线程忙(即它们没有进入等待状态),当我尝试做第五个连接,完成包不会出列因此会挂起!然而,这种情况并非如此;我可以与我的服务器建立5个甚至6个连接!这表明即使我的最大允许线程数(4)已经运行,我仍然可以将数据包出列? 这就是为什么我感到困惑!

1 个答案:

答案 0 :(得分:1)

完成端口 - 实际上是KQUEUE对象。 NumberOfConcurrentThreads 对应MaximumCount

  

队列可以满足的最大并发线程数等待。

来自I / O完成端口

  

当与之关联的可运行线程的总数时   完成端口达到并发值,系统阻塞   执行与该完成相关联的任何后续线程   port,直到可运行线程数低于并发   值。

它很糟糕,并没有完全说出来。当线程调用KeRemoveQueueGetQueuedCompletionStatus内部调用它)系统将数据包返回到线程only if Queue->CurrentCount < Queue->MaximumCount,即使队列中存在数据包。系统不阻止任何线程当然。从另一边寻找KiInsertQueue - 即使某些线程在数据包上等待 - 它仅在Queue->CurrentCount < Queue->MaximumCount的情况下被激活。

还会查看Queue->CurrentCount的更改方式和时间。查找KiActivateWaiterQueue当前线程即将进入等待状态时调用此函数)和KiUnlinkThread。通常 - 当线程开始等待任何对象(或另一个队列)系统调用KiActivateWaiterQueue时 - 它会递减CurrentCount并且可能(如果队列中存在数据包并且变为Queue->CurrentCount < Queue->MaximumCount并且线程等待数据包)返回数据包等待线程。另一方面,当线程停止等待时 - KiUnlinkThread被调用。它增加CurrentCount

你的两种变体都是错的。任何线程数都可以调用GetQueuedCompletionStatus()。系统当然不会阻止任何后续线程的执行。例如 - 你有MaximumCount = 4的队列。你可以将10个数据包排队到队列。从并发的7个线程中调用GetQueuedCompletionStatus()。但只有4个来自它的包。另一个将等待(尽管队列中还有6个数据包)。如果从队列中删除数据包的某些线程开始等待 - 系统只是等待并将数据包返回到另一个线程等待队列。或者如果线程(之前已经从此队列中移除数据包(Thread->Queue == Queue) - 所以活动线程)再次调用KeRemoveQueue将是Queue->CurrentCount -= 1;