在将CompletionRoutine与多个套接字一起使用时,如何同步WSARecvFrom()的处理

时间:2014-06-13 10:39:49

标签: c multithreading sockets winapi

来自MSDN文档:

  

传输提供程序允许应用程序在套接字I / O完成例程的上下文中调用发送和接收操作,并保证对于给定的套接字,I / O完成例程不会嵌套。这允许时间敏感的数据传输完全在抢先的上下文中发生。

在我们的系统中,我们有一个线程为多个套接字调用WSARecvFrom()。该线程有一个CompletionRoutine处理来自WSARecvFrom()opverlapped I / O的所有回调。

我们的测试显示,此完成例程被称为从中断触发。在仍然从另一个套接字处理完整例程时调用套接字。

我们怎样才能防止这个完成例程在处理来自另一个套接字的输入时没有被调用?

我们可以使用哪种数据处理序列化?

请注意,有接收和发送实时数据的套接字。与等待多个对象同步不适用,因为Win32 API最多定义了64个。

我们不能使用信号量,因为当新调用时,旧的正在进行的处理被中断,因此信号量将不会被重新发布,并且新的处理块将永远存在。

关键部分或互斥锁不是一个选项,因为完成例程回调是在同一个线程内进行的,因此无论如何CS或互斥都会接受,并且不会等到旧处理结束。

有没有人有想法甚至更好的方法来序列化(同步)​​数据处理?

2 个答案:

答案 0 :(得分:4)

如果您再次仔细阅读WSARecvFrom() documentation,也会说:

  

完成例程遵循与Windows文件I / O完成例程相同的规则。在线程处于可警告的等待状态之前,不会调用完成例程,例如当调用fAlertable参数设置为TRUE的函数WSAWaitForMultipleEvents时可能会发生。

Alertable I/O documentation然后声明:

  

当线程进入可警告状态时,会发生以下事件:

     
      
  1. 内核检查线程的APC队列。如果队列包含回调函数指针,则内核会从队列中删除指针并将其发送到线程。
  2.   
  3. 该线程执行回调函数。
  4.   
  5. 对队列中剩余的每个指针重复步骤1和2。
  6.   
  7. 当队列为空时,线程从使其置于可警告状态的函数返回。
  8.   

因此,给定线程实际上不可能将多个挂起的完成例程重叠在一起,因为线程以序列化方式接收和处理例程。我可以看到不同的唯一方法是,如果完成例程正在执行某些操作以将线程置于第二个可警告状态,而之前的可警告状态仍然有效。我不确定在那种情况下Windows会做什么,但你应该避免这样做。

  

请注意,有接收和发送实时数据的套接字。与等待多个对象同步不适用,因为Win32 API

定义了最多64个

WaitForMultipleObjects() documentation告诉您如何解决该限制:

  

要等待超过MAXIMUM_WAIT_OBJECTS句柄,请使用以下方法之一:

     

•创建一个线程以等待MAXIMUM_WAIT_OBJECTS句柄,然后等待该线程加上其他句柄。使用此技术将句柄分成MAXIMUM_WAIT_OBJECTS组。

     

•调用RegisterWaitForSingleObject在每个句柄上等待。来自线程池的等待线程在MAXIMUM_WAIT_OBJECTS注册的对象上等待,并在发出对象信号或超时间隔到期后分配工作线程。

无论如何我都不会在套接字上等待,这不是很有效。只要他们做安全的事情,使用完成例程就可以了。

否则,我建议您停止使用完成例程并转而使用I/O Completion Port代替套接字I / O.然后,您可以更好地控制何时向您报告完成结果,因为您必须自己调用GetQueuedCompletionStatus()以获取每个I / O操作的结果。您可以将多个套接字与单个IOCP相关联,然后在该IOCP上调用GetQueuedCompletionStatus()的小线程池(通常每个CPU核心一个线程最佳)。这样,您可以并行处理多个I / O结果,因为它们将位于不同的线程上下文中,并且不能在同一个线程中相互重叠。但这确实意味着您可以在一个线程中执行I / O操作,结果可能会显示在不同的线程中。只需确保您的完成处理是线程安全的。

答案 1 :(得分:0)

首先,我要感谢所有有用的提示和评论。

我们现在停止使用完成例程。我们将应用程序更改为使用完成端口。

我们对完成例程的最大问题是每次线程进入可警告状态时,可以(并且将)再次从OS调用完成例程。如调试器中所示,从完成例程内部调用WSASendTo()会将线程置于可警告状态。因此,在完成例程的上一次执行结束之前,再次执行完成例程。

这使得几乎无法同步来自多个不同套接字的数据处理。

使用Completion Ports的方法似乎是完美的。然后,当您从GetQueuedCompletionStatus()释放以处理数据缓冲区时,您可以控制正在执行的操作。您必须自己以线性方式同步数据处理,而不会在尝试处理数据时被中断和重新执行。