Які відмінності між різними параметрами синхронізації потоків у C #?


164

Чи може хтось пояснити різницю між:

  • замок (деякийоб'єкт) {}
  • Використання Mutex
  • Використання Семафору
  • Використання монітора
  • Використання інших класів синхронізації .Net

Я просто не можу це зрозуміти. Мені здається, перші два такі ж?


Це посилання мені дуже допомогло: albahari.com/threading
Рафаель

Відповіді:


135

Чудове запитання. Я, можливо, помиляюсь. Дозвольте спробувати .. Редакція №2 моєї відповіді "Оріс". Дякуємо, що змусили мене читати :)

замок (obj)

  • є конструкцією CLR, яка використовується для (внутрішньооб'єктної?) синхронізації потоку. Забезпечує, що лише один потік може взяти право власності на блокування об'єкта та ввести заблокований блок коду. Інші потоки повинні зачекати, поки поточний власник відмовиться від блокування, вийшовши з блоку коду. Також рекомендується заблокувати приватний об'єкт-член вашого класу.

Монітори

  • lock (obj) реалізується внутрішньо за допомогою Монітора. Вам слід віддати перевагу lock (obj), тому що це заважає вам збитися, як забути процедуру очищення. Якщо ви хочете, це "ідіот-доказ" - це конструкція "Монітор".
    Використання Монітора, як правило, більше перевагу над мютексами, оскільки монітори були розроблені спеціально для .NET Framework і тому краще використовують ресурси.

Використання блокування або монітора корисно для запобігання одночасному виконанню потокових блоків коду, але ці конструкції не дозволяють одному потоку повідомляти подію іншому. Для цього потрібні події синхронізації , які є об'єктами, що мають один із двох станів, сигналізований та несигналізований, які можна використовувати для активації та призупинення потоків. Mutex, Semaphores - це концепції на рівні ОС. наприклад, з іменованим mutex ви можете синхронізувати через декілька (керованих) exe (гарантуючи, що на пристрої працює лише один екземпляр вашої програми.)

Mutex:

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

Семафори (боляче мій мозок).

  • Використовуйте клас Semaphore для контролю доступу до пулу ресурсів. Нитки вводять семафор, викликаючи метод WaitOne, який успадковується від класу WaitHandle, і звільняють семафор, викликаючи метод Release. Кількість семафору зменшується щоразу, коли нитка потрапляє в семафор, і збільшується, коли нитка випускає семафор. Коли кількість дорівнює нулю, наступні запити блокують, поки інші потоки не звільняють семафор. Коли всі потоки випустили семафор, підрахунок знаходиться на максимальному значенні, визначеному під час створення семафору. Нитка може вводити семафор кілька разів. Клас Semaphore не примушує ідентифікацію потоку на WaitOne або Release .. відповідальність програмістів за те, що вони не мукуються. Семафори бувають двох типів: локальні семафори та названісистемні семафори. Якщо ви створюєте об'єкт Semaphore за допомогою конструктора, який приймає ім'я, він асоціюється з семафором операційної системи цього імені. Названі системні семафори видно по всій операційній системі, і їх можна використовувати для синхронізації діяльності процесів. Місцевий семафор існує лише у вашому процесі. Він може бути використаний будь-яким потоком у вашому процесі, який має посилання на локальний об'єкт Semaphore. Кожен об’єкт Семафора - це окремий локальний семафор.

СТОРІНКА, ЩО ЧИТАТИ - Синхронізація теми (C #)


18
Ви стверджуєте, що Monitorне дозволяє комунікація невірна; Ви все ще можете і Pulseт. д. зMonitor
Марк Гравелл

3
Ознайомтесь з альтернативним описом Semaphores - stackoverflow.com/a/40473/968003 . Подумайте про семафори як про відмов у нічному клубі. Існує виділена кількість людей, яких дозволено одразу в клубі. Якщо клуб заповнений, нікого не можна входити, але як тільки одна людина покине іншу людину, може вступити.
Олексій Клаус

31

Повторно "Використання інших класів синхронізації .Net" - деякі з інших, про які вам слід знати:

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

У CCR / TPL ( Parallel Extensions CTP) є також більше (низькі накладні) замикаючі конструкції - але IIRC, вони будуть доступні в .NET 4.0


Тож якщо я хочу простого сигнального зв’язку (скажімо, завершення асинхронного режиму) - я повинен Monitor.Pulse? або використовувати SemaphoreSlim або TaskCompletionSource?
Вівек

Використовуйте TaskCompletionSource для роботи з асинхронізацією. В основному, перестаньте думати про нитки і починайте думати про завдання (одиниці роботи). Нитки - це деталь реалізації та не мають значення. Повертаючи TCS, ви можете повернути результати, помилки або обробити скасування, і це легко поєднується з іншими операціями з асинхронізацією (наприклад, async await або ContinueWith).
Саймон Гіллбі

14

Як зазначено в ECMA, і як ви можете помітити з Reflected методів, твердження про блокування в основному еквівалентно

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

З вищезгаданого прикладу ми бачимо, що Монітори можуть блокувати об’єкти.

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

Семафори схожі на мутекси на стероїдах, вони дозволяють одночасний доступ, забезпечуючи максимальну кількість одночасного доступу '. Після досягнення межі семафор починає блокувати подальший доступ до ресурсу, поки один з абонентів не звільнить семафор.


5
Цей синтаксичний цукор був дещо змінений у C # 4 Перевірте blogs.msdn.com/ericlippert/archive/2009/03/06/…
Пітер Гфадер

14

Я робив класи та підтримку CLR для потоків в DotGNU, і у мене є кілька думок ...

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

Як згадуються інші, заява блокування C # є магією компілятора для Monitor.Enter та Monitor.Exit (існує в межах спроби / нарешті).

Монітори мають простий, але потужний механізм сигналу / очікування, якого у Mutexes немає за допомогою методу Monitor.Pulse / Monitor.Wait. Еквівалент Win32 буде об'єктами події через CreateEvent, які фактично також існують у .NET як WaitHandles. Модель Pulse / Wait схожа на pthread_signal та pthread_wait Unix, але вони швидші, тому що вони можуть бути цілком операційними в користувальницькому режимі в непротиворечатому випадку.

Monitor.Pulse / Wait простий у використанні. В одному потоці ми замикаємо об'єкт, перевіряємо прапор / стан / властивість, і якщо це не те, що ми очікуємо, зателефонуйте на Monitor. Зачекайте, що вимкне замок і чекаємо, поки імпульс буде надісланий. Коли очікування повернеться, ми повертаємося назад і ще раз перевіряємо прапор / стан / властивість. В іншому потоці ми блокуємо об'єкт щоразу, коли ми змінюємо прапор / стан / властивість, а потім викликаємо 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

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

Поставте свій ідентифікатор рядка за допомогою "Глобального \", щоб забезпечити належний контроль доступу до спільних системних ресурсів. Я просто зіткнувся з цілою купою проблем із синхронізацією зв’язку зі службою, що працює під обліковим записом SYSTEM, перш ніж я зрозумів це.


5

Я б спробував уникнути "lock ()", "Mutex" та "Monitor", якщо можете ...

Ознайомтеся з новим простором імен System.Collections.Concurrent в .NET 4 У
ньому є кілька приємних класів колекцій, безпечних для потоків.

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

Одночасні словникові скелі! більше немає ручного блокування для мене!


2
Уникайте блокування, але використовуйте Монітор? Чому?
мафу

@mafutrct Тому що вам потрібно подбати про синхронізацію самостійно.
Пітер Гфадер

О, зараз я зрозумів, ти мав на увазі уникати ВСІХ з трьох згаданих ідей. Здавалося, ви користуєтеся Монітором, але не використовуєте замок / Mutex.
мафу

Ніколи не використовуйте System.Collections.Concurrent. Вони є основним джерелом умов перегонів, а також блокують нитку абонентів.
Олександр Данилов

-2

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

І ви точно не повинні використовувати System.Collections.Concurrent класи - вони є основним джерелом перегонових умов, оскільки не підтримують транзакції між декількома колекціями, а також блокують поточну нитку.

Дивно. NET не має ефективних механізмів синхронізації.

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

Це найкращий спосіб синхронізувати що-небудь у більшості випадків - від доступу до бази даних (привіт sqlite) до бізнес-логіки.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.