我有一个多线程网络应用程序,它使用UdpClient、TcpClient、TcpListener,并使用BeginReceive() EndReceive()回调模式处理接收到的连接和接收的数据。
以UdpClient为例,在这种模式中,我使用的一般工作流程是:
UdpClient.BeginReceive()UdpClient.EndReceive()收集数据报。UdpClient.BeginReceive(),准备接收另一个数据报。Q:由于只有一个UdpClient对象,并且由于在下一个BeginReceive()之前总是调用EndReceive()的模式,是否有必要为这些调用锁定/同步对UdpClient对象的访问?
在我看来,另一个线程不可能干预这个工作流或使那些调用非原子化。TcpClient.BeginReceive()和TcpListener.BeginAcceptTcpClient()的模式非常相似。
奖励Q:单个UdpClient对象是否需要声明为static (如果需要static锁object )?
注意:我是而不是,询问是否需要在数据报处理期间执行任何锁定。只涉及此模式和UdpClient TcpClient TcpListener对象。
编辑
作为澄清,(忽略异常处理)是这样的代码:
private void InitUDP()
{
udpclient = new UdpClient(new IPEndPoint(IPAddress.Any, Settings.Port));
udpclient.BeginReceive(new AsyncCallback(receiveCallback), udpclient);
}
private void receiveCallback(IAsyncResult ar)
{
UdpClient client = (UdpClient)ar.AsyncState;
IPEndPoint ep = new IPEndPoint(IPAddress.Any, 0);
byte[] datagram = client.EndReceive(ar, ref ep);
udpclient.BeginReceive(new AsyncCallback(receiveCallback), udpclient);
processDatagram();
}实际上与此代码不同或保护程度较低:
private void InitUDP()
{
udpclient = new UdpClient(new IPEndPoint(IPAddress.Any, Settings.Port));
udpclient.BeginReceive(new AsyncCallback(receiveCallback), udpclient);
}
private void receiveCallback(IAsyncResult ar)
{
UdpClient client = (UdpClient)ar.AsyncState;
IPEndPoint ep = new IPEndPoint(IPAddress.Any, 0);
lock(_lock)
{
byte[] datagram = client.EndReceive(ar, ref ep);
udpclient.BeginReceive(new AsyncCallback(receiveCallback), udpclient);
}
processDatagram();
}发布于 2015-10-14 04:59:51
是否有必要为这些调用锁定/同步对UdpClient对象的访问?
不,不完全是,但也许不是因为你可能认为的原因。
如果在处理当前数据报之前调用BeginReceiveFrom() (或仅调用BeginReceive() ),则实际上可以同时调用相同的回调。这是否真的会发生取决于许多事情,包括线程调度、线程池中当前可用的IOCP线程数量,当然还有是否有数据报可接收。
因此,您肯定存在这样的风险:在处理当前数据报之前,将发生新数据报的接收,并且在完成第一个数据报的处理之前将开始对该数据报的处理。
现在,如果数据报的处理涉及对其他共享数据的访问,那么您肯定需要围绕其他共享数据进行同步,以确保对其他数据的安全访问。
但就数据报本身而言,网络对象是线程安全的,因为您不会同时使用…损坏对象。确保你以连贯的方式使用它们仍然取决于你。但是对于UDP协议来说,这比TCP更容易。
UDP不可靠。它缺乏三个非常重要的保障:
最后一点在这里特别重要。您的代码已经能够处理无序的数据报处理。因此,无论是由于网络本身,还是由于您在处理当前数据报之前启动了一个新的I/O操作,如果您的代码编写正确,那么您的代码将成功地处理它。
有了TCP,情况就不同了。如果您已经启动了I/O操作,那么在处理当前的I/O操作之前,它肯定会完成。但是与UDP不同的是,您确实对TCP有一些保证,包括在套接字上接收的数据将按照发送数据的相同顺序接收。
因此,只要您在完全处理当前已完成的I/O操作之前不调用BeginReceive(),一切都会好起来的。您的代码以正确的顺序查看数据。但是,如果您之前调用了BeginReceive(),那么您的当前线程在完成当前I/O操作之前可能会被抢占,而另一个线程可能最终处理一个新完成的I/O操作。
除非您已经对接收到的数据进行了某种同步或排序,以便考虑到处理I/O完成不正常的可能性,否则这将破坏您的数据。不太好。
有充分的理由同时执行多个接收操作。但是,它们通常与对高可伸缩性服务器的需求有关。还存在与发出多个并发接收操作相关的负面因素,包括确保按正确顺序处理数据所增加的复杂性,以及在堆中有多个固定/固定缓冲区的开销(尽管这可以通过多种方式减轻,例如分配足够大的缓冲区以确保它们在大对象堆中)。
我将避免以这种方式实现代码,除非您必须解决特定的性能问题。即使在处理UDP时,尤其是在处理TCP时。如果您确实以这种方式实现代码,请非常小心地执行它。
单个UdpClient对象是否需要声明为静态对象(如果需要静态锁定对象,则需要静态锁定对象)?
存储对UdpClient对象的引用的位置一点也不重要。如果您的代码需要一次维护多个UdpClient,那么在单个UdpClient-type字段中存储引用甚至不太方便。
static所做的一切就是改变访问该成员的方式。如果不是static,则需要指定找到成员的实例引用;如果是static,则只需指定类型。就这样。它与线程安全本身一点关系都没有。
最后,对于两个代码示例,它们在功能上是等价的。不需要保护对EndReceive()和BeginReceive()的调用,而且您的lock不包含这些方法的任何其他部分(例如,数据报的实际处理),因此它没有真正完成任何事情(除了可能增加上下文交换机的开销)。
在并发场景中,第一个线程可能在离开lock之前被抢占,但在调用BeginReceive()之后。这可能导致第二个线程被唤醒,以便在第二个I/O完成过程中处理回调。第二个线程将命中lock并停止运行,允许第一个线程继续执行并离开lock。但是同步所做的一切只是让事情慢下来。它不阻止对数据报数据本身的任何并发访问,这是(可能的)重要部分。
https://stackoverflow.com/questions/33114541
复制相似问题