Подтвердить что ты не робот

Когда использовать блокировку в С#?

У меня есть сервер, который обрабатывает несколько входящих соединений сокетов и создает 2 разных потока, которые хранят данные в формате XML.

Я использовал инструкцию lock для безопасности потоков почти в каждом обработчике событий, называемом асинхронно, и в двух потоках в разных частях кода. К сожалению, с помощью этого подхода мое приложение значительно замедляется.

Я попытался вообще не использовать lock, и сервер работает очень быстро, даже файловое хранилище, похоже, увеличивает; но программа вылетает по причинам, которые я не понимаю после 30 секунд - 1 мин. работы.

Итак. Я думал, что лучший способ - использовать меньше замков или использовать его только там, где это строго необходимо. Таким образом, у меня есть 2 вопроса:

  • Является ли блокировка необходимой, когда я пишу только общедоступным переменным (спискам С#) или даже когда я их читаю?

  • Является ли блокировка необходимой только в асинхронных потоках, созданных обработчиком сокета или в других местах?

Кто-то может дать мне некоторые практические рекомендации, о том, как действовать. На этот раз я не буду публиковать весь код. Не имеет смысла размещать около 2500 строк кода.

4b9b3361

Ответ 1

Является ли блокировка необходимой, когда я пишу только общедоступным переменным (спискам С#) или даже когда я их читаю?

Да (даже когда вы читаете).

Является ли блокировка необходимой только в асинхронных потоках, созданных обработчиком сокета или в других местах?

Да. Где бы код не обращался к разделу общего кода, всегда блокировать.


Похоже, что вы не можете блокировать отдельные объекты, но блокировать одну вещь для всех ситуаций блокировки.

Если это положить в интеллектуальные дискретные блокировки, создав отдельные уникальные объекты, которые связывают и блокируют только определенные разделы за раз, которые не мешают другим потокам в других разделах.

Вот пример:

// This class simulates the use of two different thread safe resources and how to lock them
// for thread safety but not block other threads getting different resources.
public class SmartLocking
{
    private string StrResource1 { get; set; }
    private string StrResource2 { get; set; }

    private object _Lock1 = new object();
    private object _Lock2 = new object();

    public void DoWorkOn1( string change )
    {
        lock (_Lock1)
        {
            _Resource1 = change;
        }
    }

    public void DoWorkOn2( string change2 )
    {
        lock (_Lock2)
        {
            _Resource2 = change2;
        }
    }
}

Ответ 2

Вы когда-нибудь сидели в своей машине или на автобусе при красном свете, когда нет перекрестного движения? Большая трата времени, не так ли? Замок - это идеальный светофор. Он всегда зеленый, за исключением случаев, когда на пересечении есть трафик.

Ваш вопрос: "Я трачу слишком много времени на трафик, ожидая красных фонарей. Должен ли я просто запускать красный свет? Или еще лучше, должен ли я полностью отключить огни и просто позволить всем проехать через перекресток на скоростях шоссе без каких-либо управления пересечением?"

Если у вас проблемы с производительностью с блокировками, то удаление блокировок - это последнее, что вы должны сделать. Вы ждете этого красного света именно потому, что в пересечении есть перекрестное движение. Замки необычайно быстры, если они не утверждаются.

Вы не можете устранить свет, не устраняя перекрестное движение. Поэтому наилучшим решением является устранение перекрестного трафика. Если замок никогда не будет спорить, вы никогда не будете ждать его. Выясните, почему кросс-трафик тратит столько времени на перекрестке; не удаляйте свет и надейтесь, что никаких столкновений не будет. Будет.

Если вы не можете этого сделать, иногда помогает добавление более мелкозернистых замков. То есть, может быть, у вас есть все дороги в городе, сходящиеся на одном и том же перекрестке. Возможно, вы можете разбить это на два пересечения, чтобы код мог перемещаться через два разных пересечения одновременно.

Обратите внимание, что ускорение работы машины (получение более быстрого процессора) или сокращение дорог (исключение длины кода) часто приводит к ухудшению ситуации в многопоточных сценариях. Так же, как в реальной жизни; если проблема заключается в тупике, тогда покупка более быстрых автомобилей и их движение по более коротким дорогам быстрее забирают их в пробку, но не быстрее.

Ответ 3

Всегда используйте блокировку при доступе к элементам (чтение или запись). Если вы выполняете итерацию по коллекции, а из другого потока вы удаляете элементы, все может пойти не так быстро.

Предполагается, что вы хотите итерировать коллекцию, скопировать все элементы в новую коллекцию и затем перебрать копию. То есть.

var newcollection; // Initialize etc.
lock(mycollection)
{
  // Copy from mycollection to newcollection
}

foreach(var item in newcollection)
{
  // Do stuff
}

Аналогично, используйте только блокировку в тот момент, когда вы на самом деле записываете в список.

Ответ 4

Причина, по которой вам нужно блокировать чтение:

скажем, вы вносите изменения в одно свойство, и оно читается дважды, когда поток находится между блокировкой. Как только мы сделали какие-либо изменения, а потом после этого, у нас будут непоследовательные результаты.

Я надеюсь, что это поможет,

Ответ 5

В принципе на это можно ответить довольно просто:

Вам нужно заблокировать все объекты, к которым обращаются разные потоки. На самом деле это действительно не имеет значения, если он читает или пишет. Если вы читаете, а другой поток перезаписывает данные одновременно, чтение данных может оказаться недействительным и вы, возможно, выполняете недействительные операции.