Как выбрать между Semaphore и SemaphoreSlim?

109

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

Майкл Хеджпет
источник
1
Семафор всегда передает задание ОС. Это делает его относительно дорогим, если семафор не оспаривается сильно, вызов ядра легко стоит 400 наносекунд. Тонкая услуга сначала пытается сделать это дешево с общей переменной, только обращается к ОС, когда это не работает. Вы всегда любите дешевизну, за исключением крайних случаев, когда семафор должен использоваться несколькими процессами.
Hans Passant

Ответы:

64

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

В документации MSDN также указано, что SemSlim следует использовать, когда «ожидается, что время ожидания будет очень коротким». Это обычно хорошо согласуется с идеей о том, что тонкая версия более легкая для большинства компромиссов.

Эндрю Барбер
источник
66
Я бы хотел, чтобы MS написала что-нибудь о том, что происходит, когда время ожидания не "очень короткое".
Джон Рейнольдс
79
... или что означает «очень короткий»
Ричард Салай
9
В зависимости от системы, 1 микросекунда для Semaphore и 1/4 микросекунды для SemaphoreSlim для выполнения WaitOne или Release ["C # 5.0 in a Nutshell" стр. 890] Так может быть, именно это они подразумевают под очень коротким временем ожидания?
Дэвид Шеррет
2
@dhsto Хорошая ссылка! Но я предполагаю, что связанная статья означает «когда время ожидания для доступа к общему ресурсу очень короткое» - т.е. ресурс не будет оставаться в исключительном использовании в течение длительных периодов времени - а не означает время ожидания для самого кода семафора? Код семафора будет выполняться всегда, независимо от того, как долго ресурс заблокирован.
Culix
4
@culix Глядя на источник Semaphore по сравнению с SemaphoreSlim, я подозреваю, что основная причина того, что SemaphoreSlim следует использовать только для «очень коротких» ожиданий, заключается в том, что он использует ожидание вращения. Это более отзывчиво, но потребляет много ресурсов ЦП и будет расточительным для более длительных периодов ожидания, когда мгновенные ответы менее важны. Вместо этого семафор блокирует поток.
r2_118 04
17

В документации MSDN описана разница.

Одним предложением:

  • Класс SemaphoreSlim представляет собой легкий и быстрый семафор, который можно использовать для ожидания в рамках одного процесса, когда ожидается, что время ожидания будет очень коротким.
Джо
источник
1
Чтобы добавить к этому, используйте SemaphoreSlim во всех случаях, когда ваше приложение является единственным процессом на вашем компьютере, которому потребуется доступ к этому семафору.
Остин Салгат
2
@Salgat Зачем вы это делаете? Как указано в других ответах, SemaphoreSlimреализуется с использованием SpinWait, поэтому, если вы будете много ждать, вы потратите много времени процессора. Это даже не очень хорошая идея, если ваш процесс - единственный процесс на компьютере.
M.Stramm
Из документации MSDN «Класс SemaphoreSlim - рекомендуемый семафор для синхронизации в одном приложении». За исключением особых случаев, вы должны по умолчанию использовать SemaphoreSlim, если только один процесс использует этот семафор. Особые исключения обычно существуют для любого параллельного типа данных, что само собой разумеется.
Остин Салгат
17

SemaphoreSlim основан на SpinWait и Monitor, поэтому поток, ожидающий получения блокировки, в течение некоторого времени сжигает циклы ЦП в надежде получить блокировку до передачи другому потоку. Если этого не происходит, потоки позволяют системам переключать контекст и повторяют попытку (путем сжигания нескольких циклов ЦП), как только ОС снова планирует этот поток. При длительном ожидании этот шаблон может прожигать значительное количество циклов процессора. Таким образом, лучший сценарий для такой реализации - это когда большую часть времени нет времени ожидания, и вы можете почти мгновенно получить блокировку.

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

Дмитрий Захаров
источник
12

Относительно спора о «коротких временах»:

По крайней мере, в документации MSDN SemaphoreSlim указано, что

Класс SemaphoreSlim - рекомендуемый семафор для синхронизации в рамках одного приложения.

в разделе "Примечания". В том же разделе рассказывается об основных различиях между Semaphore и SemaphoreSlim:

SemaphoreSlim - это легкая альтернатива классу Semaphore, который не использует семафоры ядра Windows. В отличие от класса Semaphore, класс SemaphoreSlim не поддерживает именованные системные семафоры. Вы можете использовать его только как локальный семафор.

Шедар
источник
1
Подробнее [SemaphoreSlim and other locks] use busy spinning for brief periods before they put the thread into a true Wait state. When wait times are expected to be very short, spinning is far less computationally expensive than waiting, which involves an expensive kernel transition: dotnet.github.io/docs/essentials/collections/…
Марко Сулла,
0

Я посмотрел на исходный код здесь , и это то , что я придумал:

  1. И Semaphore, и SemaphoreSlim являются производными от WaitHandle, который внутренне использует собственный дескриптор Win32. Вот почему вам нужно использовать Dispose () как. Так что мнение о том, что Slim легкий, вызывает подозрение.

  2. SemaphoreSlim внутренне использует SpinWait, а Semaphore - нет. Это говорит мне о том, что в случаях, когда ожидается долгое ожидание, Semaphore должен работать лучше, по крайней мере, в том смысле, что он не будет загромождать ваш процессор.

ИЛИЯ БРУДНО
источник
2
SemaphoreSlim не является производным от WaitHandle. Фактически он был создан с единственной целью - исключить наследование от WaitHandle, который является примитивом пространства ядра, в задачах, где вам нужно синхронизировать операции внутри только одного процесса.
Игорь В Савченко