Каковы различия между различными параметрами синхронизации потоков в C #? - PullRequest
156 голосов
/ 19 ноября 2008

Может кто-нибудь объяснить разницу между:

  • блокировка (некий объект) {}
  • Использование Mutex
  • Использование семафора
  • Использование монитора
  • Использование других классов синхронизации .Net

Я просто не могу понять это. Мне кажется, первые два одинаковы?

Ответы [ 6 ]

130 голосов
/ 19 ноября 2008

Отличный вопрос. Возможно, я ошибаюсь .. Позвольте мне попробовать ... Редакция № 2 моего оригинального ответа ... с небольшим пониманием. Спасибо, что заставили меня прочитать:)

замок (объект)

  • - это конструкция CLR, предназначенная для (внутриобъектной?) Синхронизации потоков. Гарантирует, что только один поток может стать владельцем блокировки объекта и ввести заблокированный блок кода. Другие потоки должны ждать, пока текущий владелец не снимет блокировку, выйдя из блока кода. Также рекомендуется блокировать закрытый объект члена вашего класса.

Мониторы

  • блокировка (obj) реализована внутренне с помощью монитора. Вы должны предпочесть блокировку (obj), потому что она предотвращает обман, как вы забыли процедуру очистки. Идиотобезопасно это конструкция монитора, если хотите.
    Использование монитора обычно предпочтительнее мьютексов, потому что мониторы были разработаны специально для .NET Framework и, следовательно, лучше используют ресурсы.

Использование блокировки или монитора полезно для предотвращения одновременного выполнения чувствительных к потокам блоков кода, но эти конструкции не позволяют одному потоку передавать событие другому. Для этого требуются события синхронизации , которые представляют собой объекты с одним из двух состояний, сигнализируемых и не сигнализируемых, которые можно использовать для активации и приостановки потоков. Mutex, Семафоры - это концепции уровня ОС. например, с указанным мьютексом вы можете синхронизировать несколько (управляемых) exe (гарантируя, что на машине работает только один экземпляр вашего приложения).

Мьютекс:

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

Семафоры (повредили мне мозг).

  • Используйте класс Semaphore для управления доступом к пулу ресурсов. Потоки входят в семафор, вызывая метод WaitOne, унаследованный от класса WaitHandle, и освобождают семафор, вызывая метод Release. Счетчик семафора уменьшается каждый раз, когда поток входит в семафор, и увеличивается, когда поток освобождает семафор. Когда счетчик равен нулю, последующие запросы блокируются, пока другие потоки не освободят семафор. Когда все потоки освободили семафор, счетчик имеет максимальное значение, указанное при создании семафора. Поток может вводить семафор несколько раз. Класс Semaphore не устанавливает идентичность потока в программах WaitOne или Release .. ответственность программистов за то, что они не испортились. Семафоры бывают двух типов: локальные семафоры и именованные системные семафоры. Если вы создаете объект семафора с помощью конструктора, который принимает имя, он ассоциируется с семафором операционной системы с таким именем. Именованные системные семафоры видны во всей операционной системе и могут использоваться для синхронизации действий процессов , Локальный семафор существует только в вашем процессе. Он может использоваться любым потоком в вашем процессе, который имеет ссылку на локальный объект семафора. Каждый объект семафора - это отдельный локальный семафор.

СТРАНИЦА ДЛЯ ЧТЕНИЯ - Синхронизация потоков (C #)

28 голосов
/ 19 ноября 2008

Re "Использование других классов синхронизации .Net" - некоторые другие, о которых вы должны знать:

  • ReaderWriterLock - позволяет использовать несколько читателей или один писатель (не одновременно)
  • ReaderWriterLockSlim - как выше, более низкие издержки
  • ManualResetEvent - вентиль, позволяющий проходить код при открытии
  • AutoResetEvent - как указано выше, но автоматически закрывается при открытии

В CCR / TPL ( Parallel Extensions CTP) есть и другие (с минимальными издержками) блокирующие конструкции - но в IIRC они будут доступны в .NET 4.0

13 голосов
/ 19 ноября 2008

Как указано в ECMA, и как вы можете наблюдать из отраженных методов, оператор блокировки в основном эквивалентен

object obj = x;
System.Threading.Monitor.Enter(obj);
try {
   …
}
finally {
   System.Threading.Monitor.Exit(obj);
}

Из приведенного выше примера мы видим, что мониторы могут блокировать объекты.

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

Семафоры похожи на мьютексы на стероидах, они обеспечивают одновременный доступ, обеспечивая максимальное количество одновременных обращений ». Как только предел достигнут, семафор начинает блокировать дальнейший доступ к ресурсу, пока один из вызывающих не освободит семафор.

12 голосов
/ 06 июня 2012

Я сделал поддержку классов и CLR для потоков в DotGNU, и у меня есть несколько мыслей ...

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

Как уже упоминалось, оператор C # lock - это волшебство компилятора для Monitor.Enter и Monitor.Exit (существует в try / finally).

Мониторы имеют простой, но мощный механизм сигнала / ожидания, которого нет в мьютексах с помощью методов Monitor.Pulse / Monitor.Wait. Эквивалентом Win32 будут объекты событий через CreateEvent, которые на самом деле также существуют в .NET как WaitHandles. Модель Pulse / Wait аналогична Unix-системам pthread_signal и pthread_wait, но работает быстрее, поскольку в неконтролируемом случае они могут быть полностью операциями пользовательского режима.

Monitor.Pulse / Wait прост в использовании. В одном потоке мы блокируем объект, проверяем флаг / состояние / свойство и, если это не то, что мы ожидаем, вызываем Monitor.Wait, который снимет блокировку и будет ждать отправки импульса. Когда ожидание возвращается, мы возвращаемся назад и снова проверяем флаг / состояние / свойство. В другом потоке мы блокируем объект всякий раз, когда меняем флаг / состояние / свойство, а затем вызываем PulseAll для пробуждения любых прослушивающих потоков.

Часто мы хотим, чтобы наши классы были потокобезопасными, поэтому мы ставим блокировки в нашем коде. Однако часто бывает так, что наш класс будет когда-либо использоваться только одним потоком. Это означает, что блокировки неоправданно замедляют наш код ... именно здесь умная оптимизация в CLR может помочь повысить производительность.

Я не уверен насчет реализации Microsoft блокировок, но в DotGNU и Mono флаг состояния блокировки хранится в заголовке каждого объекта. Каждый объект в .NET (и Java) может стать блокировкой, поэтому каждый объект должен поддерживать это в своем заголовке. В реализации DotGNU есть флаг, который позволяет вам использовать глобальную хеш-таблицу для каждого объекта, который используется в качестве блокировки - это имеет преимущество, заключающееся в устранении 4-байтовых издержек для каждого объекта. Это не очень хорошо для памяти (особенно для встраиваемых систем, которые не имеют многопоточности), но сильно ухудшают производительность.

И Mono, и DotGNU эффективно используют мьютексы для выполнения блокировки / ожидания, но используют операции спин-блокировки в стиле сравнение и обмен , чтобы исключить необходимость фактически выполнять жесткие блокировки, если в этом нет особой необходимости:

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

http://cvs.savannah.gnu.org/viewvc/dotgnu-pnet/pnet/engine/lib_monitor.c?revision=1.7&view=markup

9 голосов
/ 06 апреля 2009

Дополнительным предостережением для блокировки любого общего Mutex, который вы определили с помощью идентификатора строки, является то, что он будет по умолчанию использовать мьютекс "Local \" и не будет распространяться между сеансами в среде терминального сервера.

Добавьте к строковому идентификатору префикс «Global \», чтобы обеспечить надлежащий контроль доступа к общим системным ресурсам. Я просто столкнулся с целой кучей проблем с синхронизацией связи со службой, работающей под учетной записью SYSTEM, прежде чем я понял это.

5 голосов
/ 18 мая 2010

Я бы старался избегать "lock ()", "Mutex" и "Monitor", если вы можете ...

Проверьте новое пространство имен System.Collections.Concurrent в .NET 4
У него есть несколько хороших поточно-безопасных классов коллекции

http://msdn.microsoft.com/en-us/library/system.collections.concurrent.aspx

ConcurrentDictionary пород! нет ручной блокировки больше для меня!

...