необходимо ли блокировать / синхронизировать доступ к объекту UdpClient для этих вызовов?
Нет, не совсем, но, возможно, не по той причине, о которой вы думаете.
Если вы вызываете BeginReceiveFrom()
(или просто BeginReceive()
) до того, как закончите обработку текущей дейтаграммы, фактически возможно одновременное выполнение одного и того же обратного вызова. Произойдет ли это на самом деле , зависит от многих вещей, включая планирование потоков, количество потоков IOCP, доступных в настоящее время в пуле потоков, и, конечно, есть ли даже дейтаграмма для приема.
Таким образом, у вас определенно есть риск, что до того, как вы закончите обработку текущей дейтаграммы, произойдет получение новой дейтаграммы и ее обработка начнется до того, как будет завершена обработка первой.
Теперь, если обработка дейтаграмм включает доступ к некоторым другим совместно используемым данным, то вам определенно необходима синхронизация этих других совместно используемых данных, чтобы гарантировать безопасный доступ к этим другим данным.
Но что касается самой дейтаграммы, сетевые объекты являются потокобезопасными в том смысле, что вы не повредите объект, используя его одновременно, это все еще зависит от вас, чтобы убедиться, что вы используете их согласованным образом. Но с протоколом UDP, в частности, это проще, чем с TCP.
UDP ненадежен. У него отсутствие трех очень важных гарантий:
- Нет никакой гарантии, что дейтаграмма будет доставлена.
- Нет гарантии, что дейтаграмма не будет доставлена более одного раза.
- Нет гарантии, что дейтаграмма будет доставлена в том же порядке, что и другие дейтаграммы, в которых она была отправлена.
Последний пункт особенно важен здесь. Ваш код уже должен уметь обрабатывать датаграммы не по порядку. Так что независимо от того, происходит ли это разупорядочение дейтаграмм из-за самой сети или из-за того, что вы начали новую операцию ввода-вывода до того, как закончили обработку текущей, ваш код, если он написан правильно, успешно справится с этим.
С TCP все по-другому. У вас снова возникает та же проблема, что если вы начали операцию ввода-вывода, она наверняка может завершиться до того, как вы закончите обработку текущей операции ввода-вывода. Но, в отличие от UDP, у вас есть некоторые гарантии с TCP, включая то, что данные, полученные в сокете, будут получены в том же порядке, в котором они были отправлены.
Итак, пока вы не вызываете BeginReceive()
, пока вы полностью не закончите обработку текущей завершенной операции ввода-вывода, все в порядке. Ваш код видит данные в правильном порядке. Но если вы вызовете BeginReceive()
раньше, тогда ваш текущий поток может быть освобожден до того, как он завершит обработку текущей операции ввода-вывода, а другой поток может завершить обработку недавно завершенной операции ввода-вывода.
Если вы не выполнили какую-либо синхронизацию или упорядочение полученных данных, чтобы учесть возможность обработки неупорядоченных операций ввода-вывода, это повредит ваши данные. Нехорошо.
Есть веские причины для одновременного выполнения нескольких операций приема. Но обычно они связаны с потребностью в высокомасштабируемом сервере. Есть также недостатки, связанные с выполнением нескольких одновременных операций приема, в том числе дополнительная сложность обеспечения обработки данных в правильном порядке, а также накладные расходы, связанные с наличием нескольких фиксированных / закрепленных буферов в вашей куче (хотя это можно уменьшить в различные способы, такие как выделение достаточно больших буферов, чтобы они находились в куче больших объектов).
Я бы не стал реализовывать код таким образом, если только у вас нет конкретной проблемы с производительностью, которую вам нужно решить. Даже при работе с UDP, но особенно при работе с TCP. И если вы действительно реализуете код таким образом, делайте это с большой осторожностью.
Нужно ли объявлять единственный объект UdpClient статическим (и объект статической блокировки, если он требуется)?
Где вы храните ссылку на ваш UdpClient
объект, не имеет значения. Если у вас есть код, который должен поддерживать более одного UdpClient
одновременно, сохранение ссылки в одном поле типа UdpClient
было бы даже не очень удобно.
Все, что делает что-то static
, - это меняет способ доступа к этому члену. Если не static
, вам нужно указать ссылку на экземпляр, в котором находится элемент; если это static
, вам просто нужно указать тип. Это все. Это не имеет ничего общего с потокобезопасностью как таковой.
Наконец, что касается двух ваших примеров кода, они функционально эквивалентны. Нет необходимости защищать вызовы EndReceive()
и BeginReceive()
, и ваш lock
не включает в себя какую-либо другую часть этих методов (например, фактическую обработку дейтаграммы), поэтому на самом деле он ничего не выполняет (кроме возможного увеличения накладных расходов переключателей контекста).
В параллельном сценарии возможно, что первый поток будет освобожден до выхода из lock
, но после вызова BeginReceive()
. Это может привести к пробуждению второго потока для обработки обратного вызова для второго завершения ввода-вывода. Затем этот второй поток попадет в lock
и остановится, позволяя первому потоку возобновить выполнение и покинуть lock
. Но все, что делает эта синхронизация, - это замедляет работу. Это не предотвращает одновременный доступ к самим данным дейтаграммы, что (возможно) является важной частью.
person
Peter Duniho
schedule
14.10.2015
UdpClient
функции-члены не являются потокобезопасными, поэтому, если несколько потоков обращаются к одному и тому же экземпляру, у вас есть проблема. Я не уверен, был ли это ваш вопрос. - person MicroVirus   schedule 14.10.2015EndReceive()
всегда вызывается перед следующимBeginReceive()
? Мне нужно заблокировать эти звонки? - person khargoosh   schedule 14.10.2015BeginReceive
иEndReceive
. В нем также говорится об использовании задач как способе упрощения кода, что также дает понять, что в вашем коде / обратных вызовах нет потоковой передачи. - person MicroVirus   schedule 14.10.2015