Каковы различия между различными параметрами синхронизации потоков в C#?

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

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

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

6 ответов

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

Блокировка (OBJ)

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

Мониторы

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

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

мьютекс:

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

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

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

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

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

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

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

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

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

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

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

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

Я сделал поддержку классов и 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

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

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

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

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

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

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

В большинстве случаев вам не следует использовать блокировки (= мониторы) или мьютексы / семафоры. Все они блокируют текущий поток.

И вам определенно не стоит использовать System.Collections.Concurrent классы - они являются основным источником условий гонки, потому что не поддерживают транзакции между несколькими коллекциями, а также блокируют текущий поток.

Удивительно, но.NET не имеет эффективных механизмов для синхронизации.

Я реализовал последовательную очередь из GCD (Objc/Swift world) на C# - очень легкий, не блокирующий инструмент синхронизации, использующий пул потоков, с тестами.

В большинстве случаев это лучший способ синхронизировать что-либо - от доступа к базе данных (привет, sqlite) до бизнес-логики.

Другие вопросы по тегам