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

Платформа .NET предоставляет ряд типов для синхронизации доступа к общему ресурсу или координации взаимодействия потоков.

Внимание

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

Класс WaitHandle и типы упрощенной синхронизации

Несколько примитивов синхронизации .NET являются производными от класса System.Threading.WaitHandle, который инкапсулирует собственный дескриптор операционной системы синхронизации и использует механизм сигнализации для взаимодействия потоков. К ним относятся такие классы:

  • System.Threading.Mutex, который предоставляет монопольный доступ к общему ресурсу. Если мьютексом не владеет ни один поток, сообщается состояние мьютекса.
  • System.Threading.Semaphore, ограничивающий число потоков, которые могут одновременно обращаться к ресурсу или пулу ресурсов. Состояние семафора становится сигнальным, когда это число становится больше нуля, и несигнальным, когда равно нулю.
  • System.Threading.EventWaitHandle, который представляет событие синхронизации потоков и может быть в сигнальном или несигнальном состоянии.
  • System.Threading.AutoResetEvent, который является производным от EventWaitHandle и при получении сигнала автоматически сбрасывается в сигнальное состояние после освобождения одиночного потока в состоянии ожидания.
  • System.Threading.ManualResetEvent, который является производным от EventWaitHandle и при получении сигнала остается в сигнальном состоянии до вызова метода Reset.

Так как WaitHandle наследует System.MarshalByRefObject, в .NET Framework эти типы можно использовать для синхронизации действий потоков за пределами доменов приложений.

В .NET Framework, .NET Core и .NET 5 или более поздней версии некоторые из этих типов могут быть именованными дескрипторами синхронизации системы, которые видны в операционной системе и могут использоваться для синхронизации между процессами.

Дополнительные сведения см. в справочной документации по API WaitHandle.

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

Некоторые из них являются альтернативой типам, производным от WaitHandle. Например, SemaphoreSlim является упрощенной альтернативой Semaphore.

Синхронизация доступа к общему ресурсу

Платформа .NET предоставляет ряд примитивов синхронизации для управления доступом нескольких потоков к общему ресурсу.

Monitor - класс

Класс System.Threading.Monitor предоставляет монопольный доступ к общему ресурсу, блокируя или разблокируя объект, определяющий ресурс. Во время блокировки поток, удерживающий блокировку, может снова поставить и снять блокировку. Любой другой поток не может получить блокировку, и метод Monitor.Enter ожидает снятия блокировки. Метод Enter получает снятую блокировку. Можно также использовать метод Monitor.TryEnter, чтобы задать количество времени, в течение которого поток пытается получить блокировку. Так как класс Monitor реализует привязку потока, поток, который получил блокировку, должен снять ее, вызвав метод Monitor.Exit.

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

Дополнительные сведения см. в справочной документации по API Monitor.

Примечание.

С помощью операторов lock в C# и SyncLock в Visual Basic можно синхронизировать доступ к общему ресурсу вместо использования класса Monitor напрямую. Эти операторы реализуются с помощью методов Enter, Exit и блока try…finally, обеспечивающих постоянное снятие полученной блокировки.

Mutex класс

Класс System.Threading.Mutex, как и Monitor, предоставляет монопольный доступ к общему ресурсу. С помощью вызова одной из перегрузок метода Mutex.WaitOne можно запросить владение мьютексом. Как и Monitor, Mutexреализует привязку потока, и поток, который получил мьютекс, должен освободить его, вызвав метод Mutex.ReleaseMutex.

В отличие от Monitor, класс Mutex может использоваться для межпроцессной синхронизации. Для этого нужно использовать именованный мьютекс, который виден в операционной системе. Чтобы создать экземпляр именованного мьютекса, используйте конструктор Mutex, который задает имя. Также можно вызвать метод Mutex.OpenExisting, чтобы открыть существующий именованный системный мьютекс.

Дополнительные сведения см. в статье о мьютексах и справочной документации по API Mutex.

Структура SpinLock

Структура System.Threading.SpinLock, как и Monitor, предоставляет монопольный доступ к общему ресурсу на основе доступности блокировки. Когда SpinLock пытается получить блокировку, которая недоступна, этот примитив будет ожидать в цикле, постоянно проверяя возможность получения блокировки.

Дополнительные сведения о преимуществах и недостатках использования SpinLock см. в статье о SpinLock и справочной документации по API SpinLock.

Класс ReaderWriterLockSlim

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

Дополнительные сведения см. в справочной документации по API ReaderWriterLockSlim.

Классы Semaphore и SemaphoreSlim

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

SemaphoreSlim — это упрощенная альтернатива Semaphore, которую можно использовать для синхронизации в рамках одного процесса.

В Windows можно использовать Semaphore для внутрипроцессной синхронизации. Для этого необходимо создать экземпляр Semaphore, выполняющий роль именованного системного семафора. Это можно сделать с помощью конструкторов Semaphore, которые задают имя или метод Semaphore.OpenExisting. SemaphoreSlim не поддерживает именованные системные семафоры.

Дополнительные сведения см. в статье о классах Semaphore и SemaphoreSlim и справочной документации по API Semaphore или SemaphoreSlim.

Взаимодействие потоков или сигнализация

Взаимодействие потоков (или сигнализация потоков) означает, что поток должен ждать уведомления или сигнала от одного или нескольких потоков, чтобы продолжить. Например, если поток A вызывает метод Thread.Join потока B, поток А блокируется до завершения потока B. Примитивы синхронизации, описанные в предыдущем разделе, реализуют другой механизм сигнализации: снятие блокировки потоком является сигналом другому потоку о возможности продолжать исполнение, получив блокировку.

В этом разделе описываются дополнительные сигнальные конструкции, предоставляемые .NET.

Классы EventWaitHandle, AutoResetEvent и ManualResetEvent

Класс System.Threading.EventWaitHandle представляет событие синхронизации потока.

Событие синхронизации может находиться в сигнальном или несигнальном состоянии. Если состояние события несигнальное, поток, который вызывает перегрузку события WaitOne, будет заблокирован, пока состояние события не станет сигнальным. Метод EventWaitHandle.Set задает сигнальное состояние события.

Поведение EventWaitHandle после получения сигнала зависит от его режима сброса:

  • EventWaitHandle, созданный с помощью флага EventResetMode.AutoReset, автоматически сбрасывается после освобождения одного потока в состоянии ожидания. Это похоже на турникет, пропускающий только один поток каждый раз, когда он переводится в сигнальное состояние. Такое поведение характерно для класса System.Threading.AutoResetEvent, наследующего EventWaitHandle.
  • EventWaitHandle, созданный с помощью флага EventResetMode.ManualReset, находится в сигнальном состоянии, пока не будет вызван его метод Reset. Это как ворота, которые закрыты до получения сигнала и остающиеся затем открытыми, пока кто-нибудь их не закроет. Такое поведение характерно для класса System.Threading.ManualResetEvent, наследующего EventWaitHandle. Класс System.Threading.ManualResetEventSlim является упрощенной альтернативой ManualResetEvent.

В Windows можно использовать EventWaitHandle для внутрипроцессной синхронизации. Для этого создайте экземпляр, представляющий именованное EventWaitHandle событие синхронизации системы с помощью одного из конструкторов EventWaitHandle, указывающих имя или EventWaitHandle.OpenExisting метод.

Дополнительные сведения см. в статье о EventWaitHandle. Справочные сведения об API см. здесь: EventWaitHandle, AutoResetEvent, ManualResetEvent и ManualResetEventSlim.

Класс CountdownEvent

Класс System.Threading.CountdownEvent представляет событие, возникающее, когда его счетчик имеет значение ноль. Когда CountdownEvent.CurrentCount не равен нулю, поток, который вызывает CountdownEvent.Wait, блокируется. Вызовите CountdownEvent.Signal, чтобы уменьшить значение счетчика события.

В отличие от ManualResetEvent или ManualResetEventSlim, которые позволяют разблокировать несколько потоков с помощью сигнала от одного потока, CountdownEvent может разблокировать один или несколько потоков с помощью сигналов от нескольких потоков.

Дополнительные сведения см. в статье о CountdownEvent и справочной документации по API CountdownEvent.

Класс Barrier

Класс System.Threading.Barrier представляет барьер выполнения потоков. Поток, который вызывает метод Barrier.SignalAndWait, сообщает, что он достиг барьера и ожидает, пока другие участвующие потоки не достигнут барьера. Когда все участвующие потоки достигают барьера, их выполнение продолжается, а барьер сбрасывается и может использоваться повторно.

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

Дополнительные сведения см. в статье о Barrier и справочной документации по API Barrier.

Interlocked - класс

Класс System.Threading.Interlocked предоставляет статические методы, которые выполняют простые атомарные операции над переменной. К этим атомарным операциям относится добавление, инкремент и декремент, обмен и условный обмен, зависящий от сравнения, а также операция чтения 64-разрядного целого числа.

Дополнительные сведения см. в справочной документации по API Interlocked.

Структура SpinWait

Структура System.Threading.SpinWait обеспечивает поддержку ожидания спин-блокировки. Ее можно использовать, когда потоку нужно дождаться возникновения события или выполнения условия, но фактическое время ожидания будет меньше периода, требуемого для применения дескриптора ожидания или других способов блокировки потока. Используя SpinWait, можно задать короткий интервал времени для цикла ожидания, а затем вернуть управление (например, с помощью ожидания или спящего режима), только если условие не было выполнено в течение заданного времени.

Дополнительные сведения см. в статье о SpinWait и справочной документации по API SpinWait.

См. также