Lock, mutex, semaphore ... qual è la differenza?


Risposte:


534

Un blocco consente a un solo thread di accedere alla parte bloccata e il blocco non è condiviso con altri processi.

Un mutex è lo stesso di un blocco ma può essere a livello di sistema (condiviso da più processi).

Un semaforo fa lo stesso di un mutex ma consente di inserire un numero x di thread, questo può essere usato ad esempio per limitare il numero di task ad alta intensità di CPU, io o ram in esecuzione contemporaneamente.

Per un post più dettagliato sulle differenze tra mutex e semaforo leggi qui .

Hai anche blocchi di lettura / scrittura che consentono un numero illimitato di lettori o 1 scrittore in qualsiasi momento.


2
@mertinan non posso dire di non averne mai sentito parlare, ma questo è ciò che dice Wikipedia "Latch (database), (un blocco relativamente di breve durata) su una struttura di dati di sistema come un indice"
Peter,

2
Il monitor consente di attendere una determinata condizione (ad es. Quando viene rilasciato il blocco), "monitor".
Dzmitry Lazerka,

25
Un semaforo non è lo stesso di un mutex. Sono usati in modo molto diverso e hanno anche proprietà diverse (vale a dire per quanto riguarda la proprietà). Vedi ad esempio barrgroup.com/Embedded-Systems/How-To/RTOS-Mutex-Semaphore per i dettagli
nanoquack

3
@nanoquack sentiti libero di modificare la mia risposta se ritieni che sia fuorviante o errato.
Peter,

3
Per una più chiara distinzione tra mutex e semaforo, nel link di nanoquack, il paragrafo chiave è " L'uso corretto di un semaforo è per la segnalazione da un compito all'altro. Un mutex è pensato per essere preso e rilasciato, sempre in questo ordine, da ciascuno attività che utilizza la risorsa condivisa che protegge. Al contrario, le attività che utilizzano i semafori segnalano o attendono, non entrambe. "
ToolmakerSteve

117

Ci sono molte idee sbagliate riguardo a queste parole.

Questo è tratto da un post precedente ( https://stackoverflow.com/a/24582076/3163691 ) che si adatta perfettamente qui:

1) Sezione critica = Oggetto utente utilizzato per consentire l'esecuzione di un solo thread attivo da molti altri all'interno di un processo . Gli altri thread non selezionati (@ acquisizione di questo oggetto) vengono messi in sospensione .

[Nessuna capacità tra processi, oggetto molto primitivo].

2) Mutex Semaphore (aka Mutex) = oggetto Kernel utilizzato per consentire l'esecuzione di un solo thread attivo da molti altri, tra processi diversi . Gli altri thread non selezionati (@ acquisizione di questo oggetto) vengono messi in sospensione . Questo oggetto supporta la proprietà del thread, la notifica di terminazione del thread, la ricorsione (più chiamate "acquisisci" dallo stesso thread) e "prevenzione dell'inversione di priorità".

[Capacità tra processi, molto sicura da usare, una sorta di oggetto di sincronizzazione "di alto livello"].

3) Conteggio del semaforo (aka Semaphore) = oggetto del kernel usato per consentire l'esecuzione di un gruppo di thread attivi da molti altri. Gli altri thread non selezionati (@ acquisizione di questo oggetto) vengono messi in sospensione .

[Funzionalità tra processi tuttavia non molto sicura da usare perché manca dei seguenti attributi "mutex": notifica di terminazione del thread, ricorsione? "Evitamento dell'inversione di priorità"?, Ecc.].

4) E ora, parlando di "spinlock", prima alcune definizioni:

Regione critica = Una regione di memoria condivisa da 2 o più processi.

Lock = Una variabile il cui valore consente o nega l'ingresso in una "regione critica". (Potrebbe essere implementato come una semplice "bandiera booleana").

Attesa occupata = Test continuo di una variabile fino a quando non appare un valore.

Finalmente:

Spin-lock (aka Spinlock) = Un blocco che utilizza l' attesa occupata . (L'acquisizione del blocco è effettuata da xchg o operazioni atomiche simili ).

[Nessun thread inattivo, utilizzato principalmente solo a livello di kernel. Inefficace per il codice a livello di utente].

Come ultimo commento, non sono sicuro, ma posso scommettervi un po 'di soldi che i primi 3 oggetti di sincronizzazione (n. 1, n. 2 e n. 3) di cui sopra fanno uso di questa semplice bestia (n. 4) come parte della loro implementazione.

Buona giornata!.

Riferimenti:

-Concetti in tempo reale per sistemi integrati di Qing Li con Caroline Yao (CMP Books).

-Modern Operating Systems (3 °) di Andrew Tanenbaum (Pearson Education International).

-Applicazioni di programmazione per Microsoft Windows (4 °) di Jeffrey Richter (Microsoft Programming Series).

Inoltre, puoi dare un'occhiata a: https://stackoverflow.com/a/24586803/3163691


1
La sezione realmente critica non è un oggetto kernel, quindi più leggera e incapace di sincronizzarsi tra i processi.
Vladislavs Burakovs,

2
@ Vladislavs Burakovs: hai ragione! Perdona la mia redazione. Lo aggiusterò per motivi di coerenza.
fante,

Per una distinzione più chiara tra mutex e semaforo, come menziona nanoquack altrove, vedere barrgroup.com/Embedded-Systems/How-To/RTOS-Mutex-Semaphore - Il paragrafo chiave è " L'uso corretto di un semaforo è per la segnalazione da un compito a un altro. Un mutex è pensato per essere rilasciato e rilasciato, sempre in questo ordine, da ogni attività che utilizza la risorsa condivisa che protegge. Al contrario, le attività che usano i semafori segnalano o attendono - non entrambi. "
ToolmakerSteve

Ri congettura altri meccanismi di blocco basati su spinlock [inefficiente]: improbabile; AFAIK necessita solo di alcune operazioni atomiche più di code di sonno. Anche dove spinlock è necessario all'interno del kernel, le moderne soluzioni riducono al minimo il suo impatto come descritto in Wikipedia - Spinlock - Alternatives - " .. usa un approccio ibrido chiamato" mutex adattivo ". L'idea è di usare uno spinlock quando si tenta di accedere a una risorsa bloccata da un thread attualmente in esecuzione, ma di sospensione se il thread non è attualmente in esecuzione. (Quest'ultimo è sempre il caso su sistemi a processore singolo.) "
ToolmakerSteve

@ToolmakerSteve, ti sfido a fornire una "soluzione" senza uno "spinlock" per il problema delle "collisioni" nel tentativo di "inserire" un ID thread in una "coda di sospensione". Ad ogni modo, il testo di Wikipedia conclude che durante l'implementazione viene utilizzato uno spinlock !!!.
fante

27

La maggior parte dei problemi può essere risolta usando (i) solo blocchi, (ii) solo semafori, ... o (iii) una combinazione di entrambi! Come avrai scoperto, sono molto simili: entrambi impediscono le condizioni di gara , entrambi hanno acquire()/ release()operazioni, entrambi fanno sì che zero o più thread vengano bloccati / sospettati ... In realtà, la differenza cruciale sta solo nel modo in cui si bloccano e sbloccano .

  • Un blocco (o mutex ) ha due stati (0 o 1). Può essere sbloccato o bloccato . Vengono spesso utilizzati per garantire che un solo thread entri in una sezione critica alla volta.
  • Un semaforo ha molti stati (0, 1, 2, ...). Può essere bloccato (stato 0) o sbloccato (stati 1, 2, 3, ...). Uno o più semafori vengono spesso usati insieme per garantire che solo un thread entri in una sezione critica proprio quando il numero di unità di una risorsa ha / non ha raggiunto un valore particolare (tramite il conto alla rovescia fino a quel valore o il conteggio fino a quel valore ).

Per entrambi i blocchi / semafori, il tentativo di chiamare acquire()mentre la primitiva si trova nello stato 0 provoca la sospensione del thread che richiama. Per i blocchi: i tentativi di acquisizione del blocco sono nello stato 1 hanno esito positivo. Per i semafori: i tentativi di acquisire il blocco negli stati {1, 2, 3, ...} hanno esito positivo.

Per i blocchi nello stato stato 0, se lo stesso thread precedentemente chiamato acquire(), ora chiama il rilascio, il rilascio ha esito positivo. Se un thread diverso ha provato questo, dipende dall'implementazione / dalla libreria su cosa succede (di solito il tentativo viene ignorato o viene generato un errore). Per i semafori nello stato 0, qualsiasi thread può chiamare il rilascio e avrà esito positivo (indipendentemente dal thread acquisito in precedenza per mettere il semaforo nello stato 0).

Dalla discussione precedente, possiamo vedere che i lock hanno una nozione di proprietario (l'unico thread che può chiamare release è il proprietario), mentre i semafori non hanno un proprietario (qualsiasi thread può chiamare release su un semaforo).


Ciò che causa molta confusione è che, in pratica, sono molte varianti di questa definizione di alto livello.

Importanti variazioni da considerare :

  • Come dovrebbe essere chiamato acquire()/ release()- [Varia in modo massiccio ]
  • Il tuo lock / semaphore usa una "coda" o un "set" per ricordare i thread in attesa?
  • Il tuo blocco / semaforo può essere condiviso con thread di altri processi?
  • Il tuo lucchetto è "rientrato"? - [Di solito sì].
  • Il tuo lucchetto è "bloccante / non bloccante"? - [Normalmente i blocchi non bloccati vengono utilizzati come blocchi bloccati (ovvero spin-lock) che causano l'attesa di occupato].
  • Come assicurate che le operazioni siano "atomiche"?

Dipende dal tuo libro / docente / lingua / biblioteca / ambiente.
Ecco un breve tour per notare come alcune lingue rispondono a questi dettagli.


C, C ++ ( pthreads )

  • Un mutex è implementato tramite pthread_mutex_t. Per impostazione predefinita, non possono essere condivisi con altri processi ( PTHREAD_PROCESS_PRIVATE), tuttavia i mutex hanno un attributo chiamato pshared . Quando impostato, quindi il mutex è condiviso tra processi ( PTHREAD_PROCESS_SHARED).
  • Una serratura è la stessa cosa di un mutex.
  • Un semaforo viene implementato tramite sem_t. Analogamente ai mutex, i semafori possono essere condivisi tra i processi di molti processi o tenuti privati ​​dei thread di un singolo processo. Questo dipende dall'argomento pshared fornito a sem_init.

python ( threading.py )

  • Un lock ( threading.RLock) è per lo più lo stesso di C / C ++ pthread_mutex_t. Entrambi sono rientranti . Ciò significa che possono essere sbloccati solo dallo stesso thread che lo ha bloccato. È il caso in cui i sem_tsemafori, i threading.Semaphoresemafori e i theading.Lockblocchi non sono rientranti - poiché è possibile che qualsiasi thread possa eseguire lo sblocco del blocco / giù nel semaforo.
  • Un mutex è lo stesso di un lucchetto (il termine non è usato spesso in Python).
  • Un semaforo ( threading.Semaphore) è per lo più lo stesso di sem_t. Sebbene con sem_t, viene utilizzata una coda di ID thread per ricordare l'ordine in cui i thread sono stati bloccati quando si è tentato di bloccarlo mentre è bloccato. Quando un thread sblocca un semaforo, il primo thread nella coda (se presente) viene scelto come nuovo proprietario. L'identificatore di thread viene rimosso dalla coda e il semaforo diventa nuovamente bloccato. Tuttavia, con threading.Semaphore, viene utilizzato un set anziché una coda, quindi l'ordine in cui i thread sono stati bloccati non viene memorizzato: qualsiasi thread nel set può essere scelto come proprietario successivo.

Java ( java.util.concurrent )

  • Un lock ( java.util.concurrent.ReentrantLock) è per lo più lo stesso di C / C ++ pthread_mutex_te Python threading.RLockin quanto implementa anche un lock rientrante. La condivisione dei blocchi tra i processi è più difficile in Java a causa della JVM che funge da intermediario. Se un thread tenta di sbloccare un blocco che non possiede, IllegalMonitorStateExceptionviene lanciato un.
  • Un mutex è lo stesso di un blocco (il termine non è usato spesso in Java).
  • Un semaforo ( java.util.concurrent.Semaphore) è principalmente lo stesso di sem_te threading.Semaphore. Il costruttore per i semafori Java accetta un parametro booleano di correttezza che controlla se utilizzare un set (falso) o una coda (vero) per memorizzare i thread in attesa.

In teoria, i semafori sono spesso discussi, ma in pratica i semafori non sono usati così tanto. Un semaforo contiene solo lo stato di un numero intero, quindi spesso è piuttosto flessibile e molti sono necessari contemporaneamente - causando difficoltà nella comprensione del codice. Inoltre, il fatto che qualsiasi thread possa rilasciare un semaforo è talvolta indesiderato. Vengono invece utilizzate più primitive / astrazioni di sincronizzazione orientate agli oggetti / di livello superiore come "variabili di condizione" e "monitor".


22

Dai un'occhiata al Multithreading Tutorial di John Kopplin.

Nella sezione Sincronizzazione tra thread , spiega le differenze tra evento, blocco, mutex, semaforo, timer di attesa

Un mutex può essere di proprietà di un solo thread alla volta, consentendo ai thread di coordinare l'accesso reciprocamente esclusivo a una risorsa condivisa

Gli oggetti sezione critica forniscono una sincronizzazione simile a quella fornita dagli oggetti mutex, tranne per il fatto che gli oggetti sezione critica possono essere utilizzati solo dai thread di un singolo processo

Un'altra differenza tra un mutex e una sezione critica è che se l'oggetto della sezione critica è attualmente di proprietà di un altro thread, EnterCriticalSection()attende indefinitamente la proprietà mentre WaitForSingleObject(), che viene utilizzato con un mutex, consente di specificare un timeout

Un semaforo mantiene un conteggio tra zero e un valore massimo, limitando il numero di thread che accedono contemporaneamente a una risorsa condivisa.


15

Proverò a coprirlo con esempi:

Blocco: un esempio in cui utilizzeresti locksarebbe un dizionario condiviso in cui vengono aggiunti elementi (che devono avere chiavi univoche).
Il blocco assicurerebbe che un thread non entri nel meccanismo di codice che controlla l'elemento nel dizionario mentre un altro thread (che si trova nella sezione critica) ha già superato questo controllo e sta aggiungendo l'elemento. Se un altro thread tenta di inserire un codice bloccato, attenderà (verrà bloccato) fino al rilascio dell'oggetto.

private static readonly Object obj = new Object();

lock (obj) //after object is locked no thread can come in and insert item into dictionary on a different thread right before other thread passed the check...
{
    if (!sharedDict.ContainsKey(key))
    {
        sharedDict.Add(item);
    }
}

Semaforo: supponiamo che tu abbia un pool di connessioni, quindi un singolo thread potrebbe riservare un elemento nel pool aspettando che il semaforo ottenga una connessione. Quindi utilizza la connessione e al termine del lavoro rilascia la connessione rilasciando il semaforo.

L'esempio di codice che adoro è uno dei buttafuori dato da @Patric - ecco qui:

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace TheNightclub
{
    public class Program
    {
        public static Semaphore Bouncer { get; set; }

        public static void Main(string[] args)
        {
            // Create the semaphore with 3 slots, where 3 are available.
            Bouncer = new Semaphore(3, 3);

            // Open the nightclub.
            OpenNightclub();
        }

        public static void OpenNightclub()
        {
            for (int i = 1; i <= 50; i++)
            {
                // Let each guest enter on an own thread.
                Thread thread = new Thread(new ParameterizedThreadStart(Guest));
                thread.Start(i);
            }
        }

        public static void Guest(object args)
        {
            // Wait to enter the nightclub (a semaphore to be released).
            Console.WriteLine("Guest {0} is waiting to entering nightclub.", args);
            Bouncer.WaitOne();          

            // Do some dancing.
            Console.WriteLine("Guest {0} is doing some dancing.", args);
            Thread.Sleep(500);

            // Let one guest out (release one semaphore).
            Console.WriteLine("Guest {0} is leaving the nightclub.", args);
            Bouncer.Release(1);
        }
    }
}

Mutex È praticamente Semaphore(1,1)e spesso usato a livello globale (a livello di applicazione altrimenti lockè probabilmente più appropriato). Si userebbe global Mutexquando si elimina il nodo da un elenco accessibile a livello globale (ultima cosa che si desidera che un altro thread faccia qualcosa mentre si elimina il nodo). Quando acquisisci Mutexse un thread diverso tenta di acquisire lo stesso Mutex, verrà messo in sospensione fino a quando il thread SAME che ha acquisito lo Mutexrilascia.

Un buon esempio sulla creazione di mutex globale è di @deepee

class SingleGlobalInstance : IDisposable
{
    public bool hasHandle = false;
    Mutex mutex;

    private void InitMutex()
    {
        string appGuid = ((GuidAttribute)Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(GuidAttribute), false).GetValue(0)).Value.ToString();
        string mutexId = string.Format("Global\\{{{0}}}", appGuid);
        mutex = new Mutex(false, mutexId);

        var allowEveryoneRule = new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.FullControl, AccessControlType.Allow);
        var securitySettings = new MutexSecurity();
        securitySettings.AddAccessRule(allowEveryoneRule);
        mutex.SetAccessControl(securitySettings);
    }

    public SingleGlobalInstance(int timeOut)
    {
        InitMutex();
        try
        {
            if(timeOut < 0)
                hasHandle = mutex.WaitOne(Timeout.Infinite, false);
            else
                hasHandle = mutex.WaitOne(timeOut, false);

            if (hasHandle == false)
                throw new TimeoutException("Timeout waiting for exclusive access on SingleInstance");
        }
        catch (AbandonedMutexException)
        {
            hasHandle = true;
        }
    }


    public void Dispose()
    {
        if (mutex != null)
        {
            if (hasHandle)
                mutex.ReleaseMutex();
            mutex.Dispose();
        }
    }
}

quindi usa come:

using (new SingleGlobalInstance(1000)) //1000ms timeout on global lock
{
    //Only 1 of these runs at a time
    GlobalNodeList.Remove(node)
}

Spero che questo ti faccia risparmiare un po 'di tempo.


8

Wikipedia ha una grande sezione sulle differenze tra semafori e mutex :

Un mutex è essenzialmente la stessa cosa di un semaforo binario e talvolta utilizza la stessa implementazione di base. Le differenze tra loro sono:

I mutex hanno il concetto di proprietario, che è il processo che ha bloccato il mutex. Solo il processo che ha bloccato il mutex può sbloccarlo. Al contrario, un semaforo non ha il concetto di proprietario. Qualsiasi processo può sbloccare un semaforo.

A differenza dei semafori, i mutex forniscono la sicurezza di inversione prioritaria. Poiché il mutex conosce l'attuale proprietario, è possibile promuovere la priorità del proprietario ogni volta che un'attività con priorità più alta inizia ad attendere sul mutex.

I mutex forniscono anche sicurezza per l'eliminazione, in cui il processo che contiene il mutex non può essere cancellato accidentalmente. I semafori non lo forniscono.


5

La mia comprensione è che un mutex può essere utilizzato solo all'interno di un singolo processo, ma attraverso i suoi numerosi thread, mentre un semaforo può essere utilizzato in più processi e nei relativi set di thread.

Inoltre, un mutex è binario (è bloccato o sbloccato), mentre un semaforo ha una nozione di conteggio o una coda di più di un blocco e richieste di sblocco.

Qualcuno potrebbe verificare la mia spiegazione? Sto parlando nel contesto di Linux, in particolare Red Hat Enterprise Linux (RHEL) versione 6, che utilizza il kernel 2.6.32.


3
Ora questo potrebbe essere diverso nei diversi sistemi operativi, ma in Windows un Mutex può essere utilizzato da più processi almeno l'oggetto Mnetx .net.
Peter,

2
stackoverflow.com/questions/9389730/… "I thread all'interno dello stesso processo o all'interno di altri processi possono condividere mutex." quindi nessun mutex non deve essere specifico del processo.
Peter,

3

Usare la programmazione C su una variante Linux come caso base per esempi.

Serratura:

• Di solito un binario costrutto molto semplice in funzione sia bloccato che sbloccato

• Nessun concetto di proprietà del thread, priorità, sequenziamento ecc.

• Solitamente un blocco spin in cui il thread verifica continuamente la disponibilità dei blocchi.

• Di solito si basa su operazioni atomiche, ad esempio Test-and-set, compare-and-swap, fetch-and-add, ecc.

• Solitamente richiede il supporto hardware per il funzionamento atomico.

Blocchi file:

• Solitamente utilizzato per coordinare l'accesso a un file tramite più processi.

• Più processi possono contenere il blocco di lettura, tuttavia quando un singolo processo contiene il blocco di scrittura, nessun altro processo è autorizzato ad acquisire un blocco di lettura o scrittura.

• Esempio: flock, fcntl ecc.

mutex:

• Le chiamate di funzione Mutex di solito funzionano nello spazio del kernel e provocano chiamate di sistema.

• Usa il concetto di proprietà. Solo il thread che attualmente contiene il mutex può sbloccarlo.

• Il mutex non è ricorsivo (eccezione: PTHREAD_MUTEX_RECURSIVE).

• Di solito utilizzato in associazione con variabili di condizione e passato come argomenti ad es. Pthread_cond_signal, pthread_cond_wait ecc.

• Alcuni sistemi UNIX consentono a mutex di essere utilizzato da più processi, sebbene ciò non sia applicabile su tutti i sistemi.

Semaforo:

• Questo è un numero intero gestito dal kernel i cui valori non possono scendere al di sotto di zero.

• Può essere utilizzato per sincronizzare i processi.

• Il valore del semaforo può essere impostato su un valore maggiore di 1, nel qual caso il valore indica di solito il numero di risorse disponibili.

• Un semaforo il cui valore è limitato a 1 e 0 viene definito semaforo binario.


0

Supporting ownership, maximum number of processes share lockE maximum number of allowed processes/threads in critical sectionsono tre fattori principali che determinano il nome / tipo dell'oggetto concomitanza con nome generale lock. Poiché il valore di questi fattori è binario (hanno due stati), possiamo riassumerli in una tabella simile alla verità 3 * 8.

  • X (Supporta la proprietà?): No (0) / sì (1)
  • Y (# processi di condivisione):> 1 (∞) / 1
  • Z (# processi / thread in CA):> 1 (∞) / 1

  X   Y   Z          Name
 --- --- --- ------------------------
  0   ∞   ∞   Semaphore              
  0   ∞   1   Binary Semaphore       
  0   1   ∞   SemaphoreSlim          
  0   1   1   Binary SemaphoreSlim(?)
  1   ∞   ∞   Recursive-Mutex(?)     
  1   ∞   1   Mutex                  
  1   1   ∞   N/A(?)                 
  1   1   1   Lock/Monitor           

Sentiti libero di modificare o espandere questa tabella, l'ho pubblicato come tabella ASCII per essere modificabile :)

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.