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

164

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

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

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

user38834
источник
Эта ссылка мне очень помогла
Рафаэль

Ответы:

135

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

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

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

Мониторы

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

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

мьютекс:

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

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

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

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

Gishu
источник
18
Вы утверждаете, что Monitorне допускаете неправильное общение; вы все еще можете и PulseсMonitor
Марк Гравелл
3
Проверьте альтернативное описание семафоров - stackoverflow.com/a/40473/968003 . Думайте о семафорах как вышибалах в ночном клубе. В клубе разрешено сразу несколько человек. Если клуб заполнен, никому не разрешается входить, но как только один человек уходит, другой человек может войти.
Алекс Клаус
31

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

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

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

Марк Гравелл
источник
Так что, если я хочу простую передачу сигнала (скажем, завершение асинхронной операции) - я должен Monitor.Pulse? или использовать SemaphoreSlim или TaskCompletionSource?
Вивек
Используйте TaskCompletionSource для асинхронной операции. По сути, перестаньте думать о потоках и начните думать о задачах (единицах работы). Потоки являются деталями реализации и не имеют отношения к делу. Возвращая TCS, вы можете возвращать результаты, ошибки или обрабатывать отмену, и это легко комбинируется с другими асинхронными операциями (такими как async await или ContinueWith).
Саймон Гилби,
14

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

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

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

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

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

Arul
источник
5
Этот синтаксический сахар был немного изменен в C # 4 Ознакомьтесь с blogs.msdn.com/ericlippert/archive/2009/03/06/…
Питер Гфадер
14

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

tumtumtum
источник
9

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

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

nvuono
источник
5

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

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

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

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

Питер Гфадер
источник
2
Избегать блокировки, но использовать монитор? Зачем?
Мафу
@mafutrct Потому что вам нужно позаботиться о синхронизации самостоятельно.
Питер Гфадер
О, теперь я понял, ты хотел избежать ВСЕХ трех упомянутых идей. Это звучало так, как если бы вы использовали Monitor, но не использовали lock / Mutex.
Мафу
Никогда не используйте System.Collections.Concurrent. Они являются основным источником условий гонки, а также блокируют поток вызовов.
Александр Данилов
-2

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

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

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

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

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

Александр Данилов
источник