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

How does .NET handle IOCP thread safety?

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

需要澄清这个堆栈溢出问题:

As a side note, don't forget that your request might have completed synchronously. Perhaps you're reading from a TCP stream in a while loop, 512 bytes at a time. If the socket buffer has enough data in it, multiple ReadAsyncs can return immediately without doing any thread switching at all. [emphasis mine]

为了简单起见。让我们假设一个客户端一台服务器。服务器正在使用 IOCP。如果客户端写入速度快但服务器速度慢 reader,IOCP 是否意味着 kernel/underlying 进程可以向多个线程发出信号?

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,根据定义是:"Gets the data buffer to use with an asynchronous socket method." 因此,如果我理解正确的话,缓冲区在 SocketAsyncEventArgs 的生命周期内不应改变。

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

或者.NET框架是否同步IOCP线程?如果是这样,如果 IOCP 线程 1 阻塞了之前的读取,那么启动一个新线程有什么意义?

I've been looking but I can't seem to find how .NET handles race conditions.

在大多数情况下,事实并非如此。这取决于你。但是,从你的问题中并不清楚你是否真的有竞争条件问题。

你问的是这篇文章,在 :

If the socket buffer has enough data in it, multiple ReadAsyncs can return immediately without doing any thread switching at all

首先要明确:方法的名称是ReceiveAsync(),而不是ReadAsync()。其他 类,如 StreamReaderNetworkStreamReadAsync() 方法,这些方法与您的问题无关。现在,澄清了......

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

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

现在,让我们考虑一下您的 "fast writer, slow reader" 场景。那里可能发生的最糟糕的情况是,可能发生在任何线程中的第一次读取不会立即完成,但到引发 Completed 事件时,编写器已经超过 运行 reader的步伐。在这种情况下,由于处理 Completed 事件的一部分可能会再次调用 ReceiveAsync(),现在将 return 同步,IOCP 线程池线程将在调用中循环至 ReceiveAsync()。不需要新线程,因为当前 IOCP 线程正在同步执行所有工作。但它 确实 阻止该线程处理 其他 IOCP 事件。

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

同样,没有竞争条件。


现在,综上所述,如果您想 真的 感到困惑, 可以在 .NET 中使用异步 I/O Socket API(无论是使用 BeginReceive() 还是 ReceiveAsync(),甚至将套接字包裹在 NetworkStream 中并使用 ReadAsync()),您 do 有特定套接字的竞争条件。

我什至不愿提及它,因为在你的问题中没有任何证据表明这与你有关,也没有证据表明你真的对这种程度的细节感兴趣。添加此解释只会使事情变得混乱。但是,为了完整起见……

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

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

这不是因为读取操作本身是乱序完成的。他们没有。因此上面强调了"your"。问题是,虽然处理 IO 完成的 IOCP 线程以正确的顺序变为 运行nable(因为缓冲区按照您通过多次调用 ReceiveAsync() 提供的顺序填充),第二个 IOCP 线程成为 运行nable 可能最终成为 第一个 线程,实际被 Windows.

调度到 运行

这并不难对付。您只需要确保在发出读取操作时跟踪缓冲区序列,以便稍后可以以正确的顺序重新组装缓冲区。所有可用的异步选项都为您提供了一种机制来包含额外的用户状态数据(例如 SocketAsyncEventArgs.UserToken),因此您可以使用它来跟踪缓冲区的顺序。

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