.NET如何处理IOCP线程安全?

时间:2017-08-15 14:17:17

标签: c# .net sockets iocp

我正在玩SocketAsyncEventArgs和IO完成端口。 我一直在寻找,但我似乎无法找到.NET如何处理竞争条件。

需要澄清这个堆栈溢出问题: https://stackoverflow.com/a/28690948/855421

  

作为旁注,请不要忘记您的请求可能已同步完成。也许您在while循环中从TCP流中读取,一次512字节。 如果套接字缓冲区中有足够的数据,多个ReadAsyncs可以立即返回而不进行任何线程切换。 [强调我的]

为了简单起见。让我们假设一个客户端是一台服务器。服务器正在使用IOCP。如果客户端是快速编写器但服务器是一个慢速读取器,IOCP是否意味着内核/底层进程可以发出多个线程的信号?

1 So, socket reads 512 bytes, kernel signals a IOCP thread
2 Server processes new bytes
3 socket receives another X bytes but server is still processing previous buffer

内核是否会启动另一个线程? SocketAsyncEventArgs有一个Buffer,根据定义是:"获取与异步套接字方法一起使用的数据缓冲区。"因此,如果我理解正确的话,缓冲区不应该在SocketAsyncEventArgs的生命周期内发生变化。

什么阻止SocketAsyncEventArgs.Buffer被IOCP线程2破坏?

或者.NET框架是否同步IOCP线程?如果是这样,那么如果IOCP线程1阻止先前的读取,那么启动新线程的重点是什么?

1 个答案:

答案 0 :(得分:1)

  

我一直在寻找,但我似乎无法找到.NET如何处理竞争条件。

在大多数情况下,它没有。这取决于你做到这一点。但是,你的问题并不清楚你确实存在竞争条件问题。

您在the other answer

中询问此文字
  

如果套接字缓冲区中有足够的数据,多个ReadAsyncs可以立即返回而不进行任何线程切换

首先,要明确:方法的名称为ReceiveAsync(),而不是ReadAsync()。其他类,例如StreamReaderNetworkStream都有ReadAsync()方法,这些方法与您的问题几乎没有关系。现在,澄清......

该引用是关于竞争条件的相反。该文本的作者警告您,如果您恰好在已经准备好读取数据的套接字上调用ReceiveAsync(),则将同步读取数据并且SocketAsyncEventArgs.Completed事件将以后提出。调用ReceiveAsync()的线程也负责处理已读取的数据。

所有这一切都会发生在一个线程中。在那种情况下,不会有任何竞争条件。

现在,让我们考虑一下你的快速作家,慢读者和#34;场景。可能发生的最糟糕的情况是,第一次读取可以在任何线程中发生,但不会立即完成,但是当Completed事件被提升时,作者已经超越读者的节奏。在这种情况下,由于处理Completed事件的部分可能再次调用ReceiveAsync(),现在将同步返回,因此IOCP线程池线程将在调用{{1 }}。不需要新的线程,因为当前的IOCP线程正在同步完成所有工作。但它 阻止该线程处理其他 IOCP事件。

所有这些意味着,如果你有一些服务器正在处理的其他套接字,并且还需要调用ReceiveAsync(),那么框架必须确保那里有'可用于处理I / O的IOCP线程池中的另一个线程。但是,这是一个完全不同的套接字,无论如何你都必须为该套接字使用完全不同的缓冲区。

再次,没有竞争条件。


现在,所有这一切,如果你想让真的混淆, 可以在.NET ReceiveAsync() API中使用异步I / O(无论是否SocketBeginReceive()或甚至将套接字包裹在ReceiveAsync()并使用NetworkStream),使您具有竞争条件一个特定的插座。

我甚至不愿意提及它,因为在你的问题中没有任何证据证明这与你有关,也没有你甚至真的对这个细节水平感兴趣。添加此解释可能会让事情变得混乱。但是,为了完整起见......

在任何给定时间都可以在套接字上发出多个读操作。这有点类似于双缓冲或三缓冲视频显示(如果您熟悉该概念)。我们的想法是,在新数据进入时您可能仍在处理读取操作,并且在您处理当前读取操作之前,处理该数据的新读取操作将更加高效。

这听起来不错,但实际上由于Windows调度线程的方式,特别是不保证线程调度的特定顺序,如果您尝试以这种方式实现代码,则可能会创建您的代码将看到无序完成的读取操作。也就是说,如果你连续两次调用ReadAsync()(当然有两个不同的ReceiveAsync()对象和两个不同的缓冲区),你的SocketAsyncEventArgs事件处理程序可能会被调用先缓冲。

这不是因为读取操作本身完全无序。他们没有。因此,强调上面的"你的" 。问题是,当处理IO完成的IOCP线程以正确的顺序运行时(因为缓冲区按照您通过多次调用Completed提供的顺序填充),第二个IOCP线程将变为可运行的风成为实际安排由Windows运行的第一个线程。

这不难对付。您只需确保在发出读取操作时跟踪缓冲区序列,以便稍后可以按正确顺序重新组合缓冲区。所有可用的异步选项都提供了一种机制,您可以包含其他用户状态数据(例如ReceiveAsync()),这样您就可以使用它来跟踪缓冲区的顺序。

同样,这并不常见。对于大多数情况,完全有序的实现(仅在您完成当前读取操作后才发出新的读取操作)就足够了。如果您一直担心多缓冲区读取实现是否正确,请不要打扰。坚持简单的方法。