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

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

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

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

Эта ссылка мне очень помогла: albahari.com/threading

Raphael 08.01.2014 16:45
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
166
1
36 427
7

Ответы 7

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

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

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

Мониторы

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

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

Мьютекс:

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

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

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

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

Вы утверждаете, что Monitor не разрешает связь, неверно; вы все еще можете Pulse и т. д. с Monitor

Marc Gravell 22.07.2009 13:15

Ознакомьтесь с альтернативным описанием семафоров - stackoverflow.com/a/40473/968003. Думайте о семафорах как о вышибалах в ночном клубе. В клуб одновременно может входить определенное количество людей. Если клуб заполнен, никто не может войти, но как только один человек уходит, другой человек может войти.

Alex Klaus 13.07.2015 04:25

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

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

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

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

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

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

Peter Gfader 18.05.2010 16:03

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

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

В CCR / TPL (Параллельные расширения CTP) также есть больше (с низкими накладными расходами) блокирующих конструкций, но они будут доступны в .NET 4.0.

Итак, если мне нужна простая сигнальная связь (например, завершение асинхронной операции) - я должен Monitor.Pulse? или использовать SemaphoreSlim или TaskCompletionSource?

Vivek 10.01.2014 21:08

Используйте TaskCompletionSource для асинхронной операции. По сути, перестаньте думать о потоках и начните думать о задачах (единицах работы). Потоки являются деталью реализации и не имеют отношения к делу. Возвращая TCS, вы можете возвращать результаты, ошибки или обрабатывать отмену, и его легко комбинировать с другими асинхронными операциями (такими как async await или ContinueWith).

Simon Gillbee 13.08.2015 23:13

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

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

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

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

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

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

Избегать блокировки, но использовать монитор? Почему?

mafu 13.10.2011 12:21

@mafutrct Потому что вам нужно самому позаботиться о синхронизации.

Peter Gfader 17.10.2011 13:15

О, теперь я понимаю, вы хотели избежать ВСЕХ трех упомянутых идей. Похоже, вы использовали бы Monitor, но не использовали бы блокировку / мьютекс.

mafu 17.10.2011 17:01

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

Alexander Danilov 30.01.2020 01:35

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

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

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

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

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

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

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

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

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

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

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