Come faccio a scegliere tra Semaphore e SemaphoreSlim?


109

Le loro interfacce pubbliche sembrano simili. La documentazione afferma che SemaphoreSlim è un'alternativa leggera e non utilizza i semafori del kernel di Windows. Questa risorsa afferma che SemaphoreSlim è molto più veloce. In quali situazioni il SemaphoreSlim ha più senso rispetto al Semaphore e viceversa?


1
Il semaforo passa sempre il lavoro al sistema operativo. Ciò lo rende relativamente costoso se il semaforo non è fortemente contestato, la chiamata del kernel costa facilmente 400 nanosecondi. Il sottile favore cerca prima di farlo a buon mercato con una variabile condivisa, chiama il sistema operativo solo quando non ha funzionato. Ti piace sempre l'economico, tranne nel caso in cui il semaforo deve essere condiviso da più processi.
Hans Passant

Risposte:


64

Una differenza è che SemaphoreSlimnon consente semafori con nome, che possono essere a livello di sistema. Ciò significherebbe che un SemaphoreSlim non può essere utilizzato per la sincronizzazione tra processi.

La documentazione MSDN indica inoltre che SemSlim deve essere utilizzato quando "i tempi di attesa dovrebbero essere molto brevi". Questo di solito combacia perfettamente con l'idea che la versione slim è più leggera per la maggior parte dei compromessi.


66
Vorrei che MS avesse scritto qualcosa su ciò che accade quando i tempi di attesa non sono "molto brevi".
John Reynolds,

79
... o cosa significa "molto breve"
Richard Szalay

9
A seconda del sistema, 1 microsecondo per Semaphore e 1/4 microsecondo per SemaphoreSlim per eseguire WaitOne o Release ["C # 5.0 in a Nutshell" pag. 890] Quindi forse è questo che intendono per tempi di attesa molto brevi?
David Sherret

2
@dhsto Buon riferimento! Ma immagino che l'articolo collegato significhi "quando i tempi di attesa per accedere alla risorsa condivisa sono molto brevi" - cioè la risorsa non rimarrà in uso esclusivo per lunghi periodi di tempo - piuttosto che i tempi di attesa per il codice del semaforo stesso? Il codice del semaforo verrà sempre eseguito indipendentemente da quanto tempo la risorsa viene mantenuta bloccata.
culix

4
@culix Guardando l'origine di Semaphore rispetto a SemaphoreSlim Sospetto che il motivo principale per cui SemaphoreSlim dovrebbe essere utilizzato solo per attese "molto brevi" è perché utilizza un'attesa di rotazione. Questo è più reattivo, ma consuma molta CPU e sarebbe uno spreco per periodi di attesa più lunghi in cui le risposte istantanee sono meno importanti. Il semaforo blocca invece il thread.
r2_118

17

La documentazione MSDN descrive la differenza.

In una frase:

  • La classe SemaphoreSlim rappresenta un semaforo leggero e veloce che può essere utilizzato per l'attesa all'interno di un singolo processo quando si prevede che i tempi di attesa siano molto brevi.

1
Per aggiungere a questo, usa SemaphoreSlim in tutti i casi in cui la tua applicazione è l'unico processo sul tuo computer che avrà bisogno di accedere a quel semaforo.
Austin Salgat

2
@Salgat Perché dovresti farlo? Come delineato in altre risposte, SemaphoreSlimè implementato utilizzando SpinWait, quindi se lo aspetti molto, sprecherai molto tempo della CPU. Che non è nemmeno una buona idea se il tuo processo è l'unico processo sul computer.
M.Stramm

Dalla documentazione MSDN, "La classe SemaphoreSlim è il semaforo consigliato per la sincronizzazione all'interno di una singola app". Ad eccezione di casi speciali, dovresti utilizzare per impostazione predefinita SemaphoreSlim se solo un processo utilizza quel semaforo. In genere esistono eccezioni speciali per qualsiasi tipo di dati concorrente, il che è ovvio.
Austin Salgat

17

SemaphoreSlim è basato su SpinWait e Monitor, quindi il thread che attende di acquisire il blocco sta bruciando cicli della CPU per un po 'di tempo nella speranza di acquisire il blocco prima di cedere a un altro thread. Se ciò non accade, i thread consentono ai sistemi di cambiare contesto e riprovare (masterizzando alcuni cicli della CPU) una volta che il sistema operativo pianifica di nuovo quel thread. Con lunghe attese questo modello può bruciare attraverso una notevole quantità di cicli della CPU. Quindi lo scenario migliore per tale implementazione è quando la maggior parte delle volte non c'è tempo di attesa e puoi acquisire quasi istantaneamente il blocco.

Semaphore si basa sull'implementazione nel kernel del sistema operativo, quindi ogni volta che acquisisci il blocco, trascorri un bel po 'di cicli della CPU, ma dopo di che il thread rimane semplicemente inattivo per il tempo necessario per ottenere il blocco.


12

Per quanto riguarda la controversia sui "tempi brevi":

Almeno la documentazione di SemaphoreSlim MSDN lo afferma

La classe SemaphoreSlim è il semaforo consigliato per la sincronizzazione all'interno di una singola app.

nella sezione Osservazioni. La stessa sezione racconta anche la principale differenza tra Semaphore e SemaphoreSlim:

Il SemaphoreSlim è un'alternativa leggera alla classe Semaphore che non usa i semafori del kernel di Windows. A differenza della classe Semaphore, la classe SemaphoreSlim non supporta i semafori di sistema denominati. Puoi usarlo solo come semaforo locale.


1
Maggiori dettagli: [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 Silla

0

Ho guardato il codice sorgente qui e questo è quello che mi è venuto in mente:

  1. Sia Semaphore che SemaphoreSlim derivano da WaitHandle che utilizza internamente l'handle nativo di Win32. Ecco perché devi Dispose () entrambi. Quindi l'idea che Slim sia leggero è sospetta.

  2. SemaphoreSlim usa SpinWait internamente mentre Semaphore no. Questo mi dice che nei casi in cui l'attesa dovrebbe essere lunga, Semaphore dovrebbe fare di meglio, almeno nel senso che non soffoca la tua CPU.


2
SemaphoreSlim non è derivato da WaitHandle. In realtà è stato creato con un unico obiettivo: eliminare la derivazione da WaitHandle, che è primitiva dello spazio del kernel, in attività in cui è necessario sincronizzare le operazioni all'interno di un solo processo.
Igor V Savchenko
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.