В C # было бы лучше использовать Queue.Synchronized или lock () для безопасности потока? - PullRequest
58 голосов
/ 04 декабря 2008

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

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

Или рекомендуется использовать Queue.Synchronized следующим образом:

Queue.Synchronized(myQueue).whatever_i_want_to_do();

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

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

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

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

Ответы [ 5 ]

46 голосов
/ 04 декабря 2008

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

РЕДАКТИРОВАТЬ: Как отмечено в комментариях, если вы можете использовать абстракции более высокого уровня, это здорово. И если вы действительно используете блокировку, будьте осторожны с этим - документируйте, что вы ожидаете, где будете заблокированы, и получайте / снимайте блокировки как можно быстрее (скорее для правильности, чем для производительности). Избегайте вызова неизвестного кода, удерживая блокировку, избегайте вложенных блокировок и т. Д.

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

35 голосов
/ 04 декабря 2008

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

Существует классическое состояние гонки с синхронизированной очередью, показанное ниже, где вы проверяете Count, чтобы проверить, безопасно ли удалять из очереди, но затем метод 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 должна иметь целую кучу правильно реализованных поточно-ориентированных коллекций, но, к сожалению, это еще почти год.

15 голосов
/ 04 декабря 2008

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

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

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

7 голосов
/ 04 декабря 2008

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

object item;
if (queue.Count > 0)
{
  lock (queue)
  {
    if (queue.Count > 0)
    {
       item = queue.Dequeue();
    }
  }
}
1 голос
/ 04 декабря 2008

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...