Como faço para escolher entre Semaphore e SemaphoreSlim?

109

Suas interfaces públicas parecem semelhantes. A documentação afirma que o SemaphoreSlim é uma alternativa leve e não usa semáforos do Kernel do Windows. Este recurso afirma que o SemaphoreSlim é muito mais rápido. Em que situações o SemaphoreSlim faz mais sentido do que o Semaphore e vice-versa?

Michael Hedgpeth
fonte
1
O semáforo sempre passa o trabalho para o sistema operacional. Isso o torna relativamente caro, se o semáforo não for fortemente contestado, a chamada do kernel facilmente custa 400 nanossegundos. O pequeno favor primeiro tenta fazer isso de maneira barata com uma variável compartilhada, apenas chama o sistema operacional quando isso não funciona. Você sempre gosta de barato, exceto no caso de canto em que o semáforo precisa ser compartilhado por vários processos.
Hans Passant

Respostas:

64

Uma diferença é que SemaphoreSlimnão permite semáforos nomeados, que podem ser de todo o sistema. Isso significaria que um SemaphoreSlim não poderia ser usado para sincronização entre processos.

A documentação do MSDN também indica que SemSlim deve ser usado quando "espera-se que os tempos de espera sejam muito curtos". Isso geralmente se encaixaria bem com a ideia de que a versão fina é mais leve para a maioria das desvantagens.

Andrew Barber
fonte
66
Gostaria que a MS tivesse escrito algo sobre o que acontece quando os tempos de espera não são "muito curtos".
John Reynolds
79
... ou o que "muito curto" significa
Richard Szalay
9
Dependendo do sistema, 1 microssegundo para Semaphore e 1/4 microssegundo para SemaphoreSlim fazer um WaitOne ou Release ["C # 5.0 in a Nutshell" pág. 890] Então, talvez seja isso que eles querem dizer com tempos de espera muito curtos?
David Sherret
2
@dhsto Boa referência! Mas estou supondo que o artigo vinculado significa "quando os tempos de espera para acessar o recurso compartilhado são muito curtos" - ou seja, o recurso não permanecerá em uso exclusivo por longos períodos de tempo - em vez de significar os tempos de espera para o próprio código do semáforo? O código do semáforo sempre estará em execução, independentemente de quanto tempo o recurso é mantido bloqueado.
culix
4
@culix Olhando para a fonte de Semaphore versus SemaphoreSlim, suspeito que o principal motivo de o SemaphoreSlim ser usado apenas para esperas "muito curtas" é porque ele usa uma espera de rotação. Isso é mais responsivo, mas consome muita CPU e seria um desperdício por períodos de espera mais longos, em que as respostas instantâneas são menos importantes. Em vez disso, o semáforo bloqueia o thread.
r2_118
17

A documentação do MSDN descreve a diferença.

Em uma frase:

  • A classe SemaphoreSlim representa um semáforo leve e rápido que pode ser usado para aguardar em um único processo quando se espera que os tempos de espera sejam muito curtos.
Joe
fonte
1
Para adicionar a isso, use o SemaphoreSlim em todos os casos em que seu aplicativo é o único processo em seu computador que precisará acessar esse Semaphore.
Austin Salgat
2
@Salgat Por que você faria isso? Conforme descrito em outras respostas, SemaphoreSlimé implementado usando SpinWait, portanto, se você esperar muito, perderá muito tempo de CPU. O que nem é uma boa ideia se o seu processo for o único no computador.
M.Stramm
Na documentação do MSDN, "A classe SemaphoreSlim é o semáforo recomendado para sincronização em um único aplicativo.". Exceto em casos especiais, você deve usar o SemaphoreSlim por padrão se apenas um processo estiver usando esse semáforo. Geralmente, existem exceções especiais para qualquer tipo de dados simultâneo, o que nem é preciso dizer.
Austin Salgat
17

SemaphoreSlim é baseado em SpinWait e Monitor, então o encadeamento que espera para adquirir o bloqueio está queimando os ciclos da CPU por algum tempo na esperança de adquirir o bloqueio antes de ceder para outro encadeamento. Se isso não acontecer, os threads permitem que os sistemas alternem o contexto e tentam novamente (queimando alguns ciclos da CPU) assim que o SO agendar esse thread novamente. Com longas esperas, esse padrão pode queimar uma quantidade substancial de ciclos da CPU. Portanto, o melhor cenário para essa implementação é quando na maioria das vezes não há tempo de espera e você pode adquirir o bloqueio quase instantaneamente.

O Semaphore depende da implementação no kernel do sistema operacional, portanto, toda vez que você adquire o bloqueio, gasta muitos ciclos de CPU, mas depois disso o thread simplesmente dorme o tempo necessário para obter o bloqueio.

Dmytro Zakharov
fonte
12

Sobre a controvérsia dos "tempos curtos":

Pelo menos a documentação do SemaphoreSlim MSDN afirma que

A classe SemaphoreSlim é o semáforo recomendado para sincronização em um único aplicativo.

na seção de comentários. A mesma seção também informa a principal diferença entre Semaphore e SemaphoreSlim:

O SemaphoreSlim é uma alternativa leve para a classe Semaphore que não usa semáforos do kernel do Windows. Ao contrário da classe Semaphore, a classe SemaphoreSlim não oferece suporte a semáforos de sistema nomeados. Você pode usá-lo apenas como um semáforo local.

Shaedar
fonte
1
Mais detalhes: [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/...
Marco Sulla
0

Eu olhei o código-fonte aqui e é isso que eu vim:

  1. Tanto o Semaphore quanto o SemaphoreSlim derivam de WaitHandle, que usa internamente o identificador nativo do Win32. É por isso que você precisa Dispose () ambos. Portanto, a noção de que Slim é leve é ​​suspeita.

  2. O SemaphoreSlim usa SpinWait internamente, enquanto o Semaphore não. Isso me diz que nos casos em que se espera que a espera seja longa, o Semaphore deve se sair melhor, pelo menos no sentido de que não sufocará sua CPU.

ILIA BROUDNO
fonte
2
SemaphoreSlim não é derivado de WaitHandle. Na verdade, ele foi criado com um único objetivo - eliminar a derivação de WaitHandle, que é a primitiva do espaço do kernel, em tarefas em que é necessário sincronizar operações dentro de apenas um processo.
Igor V Savchenko