В C# было бы лучше использовать Queue.Синхронизировано или блокировка () для потокобезопасности?


у меня есть объект очереди, который мне нужно обеспечить потокобезопасность. Было бы лучше использовать объект блокировки следующим образом:

lock(myLockObject)
{
//do stuff with the queue
}

или рекомендуется использовать очередь.Синхронизировано так:

Queue.Synchronized(myQueue).whatever_i_want_to_do();

от чтения документов MSDN он говорит, что я должен использовать очередь.Синхронизировано, чтобы сделать его потокобезопасным, но затем он дает пример использования объекта блокировки. Из статьи MSDN:

гарантировать безопасность потока Очередь, все операции должны быть сделанный только через эту обертку.

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

если вызов Synchronized () не обеспечивает потокобезопасность, какой в этом смысл? Я что-то упустил?

5 58

5 ответов:

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

редактировать: как отмечено в комментариях, если вы can использовать выше уровень абстракции, это здорово. И если вы do используйте блокировку, будьте осторожны с ней - документируйте то, что вы ожидаете, чтобы быть заблокированным, и приобретайте/освобождайте блокировки в течение как можно более короткого периода (больше для корректности, чем для производительности). Избегайте вызова неизвестного кода при удержании блокировки, избегайте вложенных блокировок и т. д.

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

есть большая проблема с Synchronized методы в старой библиотеке коллекций, поскольку они синхронизируются при слишком низком уровне детализации (на метод, а не на единицу работы).

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

object item;
if (queue.Count > 0)
{
    // at this point another thread dequeues the last item, and then
    // the next line will throw an InvalidOperationException...
    item = queue.Dequeue();
}

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

object item;
lock (queue)
{
    if (queue.Count > 0)
    {
        item = queue.Dequeue();
    }
}

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

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

часто существует напряженность между требованиями к "потокобезопасным коллекциям" и требованием выполнять несколько операций над коллекцией атомарным образом.

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

а также перечисление, общие операции, такие как " is этот пункт уже в очереди? Нет, тогда я добавлю его" также требует синхронизации, которая шире, чем просто очередь.

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

object item;
if (queue.Count > 0)
{
  lock (queue)
  {
    if (queue.Count > 0)
    {
       item = queue.Dequeue();
    }
  }
}

Мне кажется ясным, что с помощью блокировки(...) {...} блокировка-это правильный ответ.

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

Если другие потоки обращаются к очереди без использования .Synchronized(), то вы будете вверх по ручью - если только весь ваш доступ к очереди не заблокирован.