带有标志FILE_SKIP_COMPLETION_PORT_ON_SUCCESS的新Windows API SetFileCompletionNotificationModes()
对于优化I/O完成端口循环非常有用,因为对于相同的HANDLE,您将获得较少的I/O完成。
但是,这也打乱了整个I/O完成端口循环,以防万一您必须进行很多更改,因此我认为最好打开一篇有关所有这些更改的文章。
首先,当您设置标志FILE_SKIP_COMPLETION_PORT_ON_SUCCESS时,这意味着您将不再接收该HANDLE/SOCKET的I/O完成,直到所有字节被读取(或写入)为止,直到没有更多I/O为止。就像在获得EWOULDBLOCK的Unix中一样。
当您再次收到ERROR_IO_PENDING(这样将有一个新请求待处理)时,就像在UNIX中获得EWOULDBLOCK一样。
话说回来,我遇到了一些困难,无法使这种行为适应我的iocp事件循环,因为普通的iocp事件循环只是永远地等待直到有一些OVERLAPPED数据包要处理时,才会处理OVERLAPPED数据包并调用正确的回调,递减一个原子计数器,然后循环开始再次等待,直到下一个数据包到来。
现在,如果我设置了FILE_SKIP_COMPLETION_PORT_ON_SUCCESS,则当返回一个OVERLAPPED数据包进行处理时,我会通过执行一些I/O(使用ReadFile()
或WSARecv()
或其他方法)对其进行处理,并且它可能会再次挂起(如果我收到ERROR_IO_PENDING),或者不能,如果我的I/O API立即完成。在前一种情况下,我只需要等待下一个未决的OVERLAPPED,但是在后一种情况下,我该怎么办?
如果我尝试执行I/O操作直到收到ERROR_IO_PENDING,它就会进入无限循环,它将永远不会返回ERROR_IO_PENDING(直到HANDLE/SOCKET的对应对象停止读取/写入),因此其他OVERLAPPED将会无限期地等待。由于我正在使用永久写入或读取的本地命名管道进行测试,因此它会陷入无限循环。
所以我想做I/O直到某个X字节的数量,就像调度程序分配时间片一样,并且如果我在X之前得到ERROR_IO_PENDING,就可以了,那么OVERLAPPED会再次在iocp事件循环中排队,但是那又如何呢?我没有收到ERROR_IO_PENDING吗?
我试图将尚未完成其I/O的OVERLAPPED放在列表/队列中以供以后处理,在处理了其他OVERLAPPED之后,稍后再调用I/O API(始终使用最大X字节量),并设置GetQueuedCompletionStatus[Ex]()
超时设置为0,因此,基本上,循环将处理尚未完成I/O的已列出/排队的OVERLAPPED,并在不休眠的情况下立即检查新的OVERLAPPED。
当未完成的OVERLAPPED的列表/队列变为空时,我可以再次将GQCS [Ex]超时设置为INFINITE。等等。
从理论上讲,它应该可以正常运行,但是我注意到了一件奇怪的事情:超时设置为0的GQCS [Ex]会返回相同的OVERLAPPED,而这些OVERLAPPED仍未完全处理(因此它们在列表/队列中,等待以后的处理)然后再次。
问题1:因此,如果我做对了,只有在处理完所有数据后,才会从系统中删除OVERLAPPED数据包?
可以说,因为如果我一次又一次得到相同的OVERLAPPED,就不需要将它们放在列表/队列中,但是我只能像处理其他OVERLAPPED一样处理它们,并且如果我得到ERROR_IO_PENDING,还可以,否则稍后再处理。
但是有一个缺陷:当我调用回调来处理OVERLAPPEDs数据包时,我会减少待处理I/O操作的原子计数器。在设置了FILE_SKIP_COMPLETION_PORT_ON_SUCCESS的情况下,我不知道是否已调用该回调函数来处理实际的挂起操作,还是仅调用OVERLAPPED来等待更多同步I/O。
问题2:如何获取该信息?我必须在从OVERLAPPED派生的结构中设置更多标志吗?
基本上,在调用ReadFile()
或WSARecv()
或其他命令之前,我会为待执行的操作增加原子计数器,并且当我看到它返回的值与ERROR_IO_PENDING或成功不同时,我将其再次递减。
在设置了FILE_SKIP_COMPLETION_PORT_ON_SUCCESS的情况下,当I/O API成功完成时,我还必须再次减小它,因为这意味着我不会收到完成。
当您的I/O API可能会立即完成并同步完成时,这会浪费时间递增和递减原子计数器。我不能仅在收到ERROR_IO_PENDING时简单地增加待处理操作的原子计数器吗?我之前没有这样做,因为我认为如果在调用线程可以检查错误是否为ERROR_IO_PENDING之前安排另一个完成我的未决I/O的线程,并因此增加未决操作的原子计数器,我将获得原子柜台搞砸了。
问题3:这是一个真正的问题吗?或者我是否可以跳过它,仅在收到ERROR_IO_PENDING时增加原子计数器?它将大大简化事情。
只是一个标志,还有很多设计要重新思考。
你觉得呢?你有没有什么想法?
最佳答案
正如雷米(Remy)在评论中所说:您对FILE_SKIP_COMPLETION_PORT_ON_SUCCESS
所做的理解是错误的。它所做的只是允许您在调用后(例如WSARecv()
返回0)来“在线”处理完成的操作。
因此,假设您拥有一个“handleCompletion()”函数,一旦您使用GQCS()从IOCP中检索完成,就可以调用该函数,只需在成功WSARecv()
之后立即调用该函数即可。
如果您使用每个操作计数器来跟踪连接上的最终操作何时完成(我这样做是为了对我作为完成 key 关联的每个连接数据进行生命周期管理),那么您仍然可以在同样的方式,没有任何改变...
您不能仅在ERROR_IO_PENDING
上递增,因为这样您就在操作完成和递增之间具有竞争条件。您必须始终在API之前递增,这可能会导致递减(在处理程序中),因为否则线程调度可能会使您烦恼。我真的看不到如何完全跳过增量...
没有其他改变。除了...
WSARecv()
调用,返回码为0(因为有可用的数据),并且您的完成处理代码可以发出另一个WSARecv()
,它也可以返回码为0,然后您的完成处理代码将再次被调用可能是递归的(取决于代码的设计)。 GQCS()
,然后使用FILE_SKIP_COMPLETION_PORT_ON_SUCCESS
,则可能会发现这些连接中的两个独占了I/O线程(所有WSARecv()
调用均返回成功,从而对所有入站数据进行内联处理)。在这种情况下,我倾向于有一个“每个连接最大连续I/O操作数”的计数器,一旦该计数器达到可配置的限制,我会将下一个内联补全发布到IOCP,并通过调用GQCS()将其检索为这使其他连接有机会。 关于c++ - SetFileCompletionNotificationModes()破坏了我的事件循环,你呢?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/22682604/