Поделиться через


Обзор примитивов синхронизации

Обновлен: Ноябрь 2007

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

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

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

Блокировка

Блокировки предоставляют единовременное управление ресурсом одному потоку или определенному количеству потоков. Поток, запрашивающий монопольную блокировку, если блокировка уже используется, блокируется до того момента, как блокировка станет доступной.

Монопольные блокировки

Самым простым способом блокировки является операция C# lock (SyncLock в Visual Basic), который управляет доступом к блоку кода. Как правило, этот блок называется критическим разделом. Оператор lock реализуется путем использования методов Enter и Exit класса Monitor, он использует try…catch…finally для обеспечения снятия блокировки.

В целом, использование оператора lock для защиты небольших блоков кода, никогда не распространяющееся на несколько методов, всегда является лучшим способом использования класса Monitor. Несмотря на все свои возможности класс Monitor подвержен потерянным блокировкам и взаимоблокировкам.

Класс Monitor

Класс Monitor предоставляет дополнительные функции, которые могут быть использованы вместе с оператором lock:

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

  • Метод Wait вызывается потоком в критическом разделе. Он предоставляет управление ресурсом и блокирует поток до того момента, как ресурс снова станет доступным.

  • Методы Pulse и PulseAll разрешают поток, который должен снять блокировку или вызвать метод Wait для помещения одного или нескольких потоков в очередь готовности, чтобы они могли получить блокировку.

Время ожидания для перегрузок метода Wait позволяет ожидающим потокам перейти в очередь готовности.

Класс Monitor может предоставить блокировку в нескольких доменах приложения, если объект использует блокировку, производную из MarshalByRefObject.

Monitor реализует сходство потоков. То есть поток, вошедший в этот класс, должен выйти посредством вызова метода Exit или Wait.

Класс Monitor не поддерживает создание экземпляров. Его методы являются статическими (Shared в Visual Basic) и работают с созданным экземпляром заблокированного объекта.

Обзор концепции см. в разделе Мониторы.

Класс Mutex

Потоки запрашивают Mutex путем вызова перегрузки этого метода WaitOne. Перегрузки со временем ожидания предоставляются для освобождения потоков после определенного времени ожидания. В отличие от класса Monitor мьютекс может быть или локальным, или глобальным. Глобальные мьютексы, также называемые именованными мьютексами, видны в операционной системе и могут использоваться для синхронизации потоков в различных доменах приложения или процессах. Локальные мьютексы происходят из MarshalByRefObject и могут использоваться за границами домена приложения.

Кроме того, Mutex происходит из WaitHandle, что означает возможность его использования в сигнальных механизмах, предоставленных WaitHandle, таким как методы WaitAll, WaitAny и SignalAndWait.

Как и класс Monitor, класс Mutex поддерживает сходство потоков. В отличие от Monitor класс Mutex является объектом, поддерживающим создание экземпляров.

Обзор концепции см. в разделе Объекты Mutex.

Другие блокировки

Блокировки не должны быть монопольными. Часто бывает полезным разрешить одновременный доступ к ресурсу ограниченному количеству потоков. Семафоры и блокировки чтения и записи предназначены для управления доступом к ресурсу в пуле.

Класс ReaderWriterLock

Класс ReaderWriterLockSlim предназначен для тех случаев, когда поток, изменяющий данные (средство записи) должен иметь монопольный доступ к ресурсу. Если средство записи не активно, любое количество средств чтения может получить доступ к ресурсу (например, посредством вызова метода EnterReadLock). Если поток запрашивает монопольный доступ (например, посредством вызова метода EnterWriteLock), последующие запросы средства чтения блокируются до того момента, как все существующие средства чтения не освободят блокировку, а средство записи не установит и не снимет блокировку.

ReaderWriterLockSlim реализует сходство потоков.

Обзор концепции см. в разделе Блокировки чтения и записи.

Класс Semaphore

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

Как и класс Mutex, класс Semaphore является производным от класса WaitHandle. Кроме того, как и класс Mutex, класс Semaphore может быть локальным или глобальным. Он может использоваться за пределами границ домена приложения.

В отличие от классов Monitor, Mutex и ReaderWriterLock, класс Semaphore не поддерживает сходство потоков. Это означает, что он может использоваться в сценариях, в которых один поток получает семафор, а другой поток освобождает его.

Обзор концепции см. в разделе Семафоры.

Сигнализация

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

Дескрипторы ожидания предоставляют более полный набор возможностей ожидания и сигнализации.

Дескрипторы ожидания

Дескрипторы ожидания являются производными из класса WaitHandle, который, в свою очередь, является производным из MarshalByRefObject. Поэтому дескрипторы ожидания могут быть использованы для синхронизации действий потоков за границами доменов приложения.

Потоки блокируются при дескрипторах ожидания посредством вызова метода экземпляра WaitOne или одного из статических методов WaitAll, WaitAny или SignalAndWait. Способ их освобождения зависит от того, как метод был вызван, а также от вида дескрипторов блокировки.

Обзор концепции см. в разделе Дескрипторы ожидания.

Дескрипторы ожидания событий

Дескрипторы ожидания событий включают класс EventWaitHandle и его производные классы AutoResetEvent и ManualResetEvent. Потоки освобождаются от дескриптора ожидания событий, если этот дескриптор получает сигнал посредством вызова метода Set или с помощью метода SignalAndWait.

Дескрипторы ожидания событий или сбрасывают себя автоматически, как турникет, разрешающий только один поток при каждом сигнале, или должны быть сброшены вручную, как ворота, которые закрыты до сигнала, а затем открыты, пока их кто-нибудь не закроет. В соответствии с их именами AutoResetEvent и ManualResetEvent представляют, соответственно, первый и второй подходы.

Дескриптор EventWaitHandle может представлять любой тип события и может быть локальным или глобальным. Производные классы AutoResetEvent и ManualResetEvent всегда являются локальными.

Дескрипторы ожидания событий не поддерживают сходство потоков. Любой поток может подать сигнал дескриптору ожидания событий.

Обзор концепции см. в разделе EventWaitHandle, AutoResetEvent и ManualResetEvent.

Классы Mutex и Semaphore

Так как классы Mutex и Semaphore являются производными из класса WaitHandle, они могут использоваться со статическими методами класса WaitHandle. Например, поток может использовать метод WaitAll до ожидания выполнения всех трех условий: подан сигнал дескриптору EventWaitHandle, освобожден мьютекс Mutex и сброшен семафор Semaphore. Так же поток может использовать метод WaitAny для ожидания выполнения одного из этих условий.

Для классов Mutex и Semaphore получение сигнала означает освобождение. Если в качестве первого аргумента метода SignalAndWait используется любой из этих типов, он освобождается. В случае класса Mutex, поддерживающего сходство потоков, исключение создается, если вызывающий поток не имеет собственного мьютекса. Как отмечалось ранее, семафоры не поддерживают сходство потоков.

Блокируемые операции

Блокируемые операции — это простые атомарные операции, выполняемые в области памяти статическими методами класса Interlocked. Эти атомарные операции включают добавление, увеличение и уменьшение, обмен, условный обмен в зависимости от сравнения, а также операции чтения для 64-разрядных значений на 32-разрядных платформах.

ms228964.alert_note(ru-ru,VS.90).gifПримечание.

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

Несмотря на то, что ни одна из этих операций не является блокировкой или сигналом, они могут быть использованы для создания блокировок и сигналов. Так как они присущи операционной системе Windows, блокируемые операции являются очень быстрыми.

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

Обзор концепции см. в разделе Блокируемые операции.

См. также

Основные понятия

Синхронизация данных для многопоточности

Мониторы

Объекты Mutex

Семафоры

Дескрипторы ожидания

Блокируемые операции

Блокировки чтения и записи

Другие ресурсы

EventWaitHandle, AutoResetEvent и ManualResetEvent