Ho sentito queste parole legate alla programmazione concorrente, ma qual è la differenza tra loro?
Ho sentito queste parole legate alla programmazione concorrente, ma qual è la differenza tra loro?
Risposte:
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.
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
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 .
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 :
acquire()
/ release()
- [Varia in modo massiccio ]Dipende dal tuo libro / docente / lingua / biblioteca / ambiente.
Ecco un breve tour per notare come alcune lingue rispondono a questi dettagli.
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
).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
.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_t
semafori, i threading.Semaphore
semafori e i theading.Lock
blocchi non sono rientranti - poiché è possibile che qualsiasi thread possa eseguire lo sblocco del blocco / giù nel 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.util.concurrent.ReentrantLock
) è per lo più lo stesso di C / C ++ pthread_mutex_t
e Python threading.RLock
in 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, IllegalMonitorStateException
viene lanciato un.java.util.concurrent.Semaphore
) è principalmente lo stesso di sem_t
e 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".
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à mentreWaitForSingleObject()
, che viene utilizzato con un mutex, consente di specificare un timeoutUn semaforo mantiene un conteggio tra zero e un valore massimo, limitando il numero di thread che accedono contemporaneamente a una risorsa condivisa.
Proverò a coprirlo con esempi:
Blocco: un esempio in cui utilizzeresti lock
sarebbe 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 Mutex
quando 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 Mutex
se un thread diverso tenta di acquisire lo stesso Mutex
, verrà messo in sospensione fino a quando il thread SAME che ha acquisito lo Mutex
rilascia.
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.
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.
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.
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.
Supporting ownership
, maximum number of processes share lock
E maximum number of allowed processes/threads in critical section
sono 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 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 :)