Esempio di mutex / tutorial?

Sono nuovo nel multithreading e stavo cercando di capire come funzionano i mutex. Ho fatto un sacco di ricerche su google e ho trovato un tutorial decente , ma ha lasciato alcuni dubbi su come funziona, perché ho creato il mio programma in cui il blocco non funzionava.

Una syntax assolutamente non intuitiva del mutex è pthread_mutex_lock( &mutex1 ); , dove sembra che il mutex sia bloccato, quando quello che voglio veramente bloccare è un’altra variabile. Questa syntax significa che il blocco di un mutex blocca una regione di codice finché il mutex non viene sbloccato? Quindi come fanno le discussioni a sapere che la regione è bloccata? [ AGGIORNAMENTO: i thread sanno che la regione è bloccata, da Memory Fencing ]. E questo fenomeno non dovrebbe essere chiamato sezione critica? [ AGGIORNAMENTO: gli oggetti della sezione critica sono disponibili solo in Windows, dove gli oggetti sono più veloci dei mutex e sono visibili solo per il thread che lo implementa. Altrimenti, la sezione critica si riferisce semplicemente all’area del codice protetto da un mutex ]

In breve, potresti aiutarmi con il programma di esempio mutex più semplice ansible e la spiegazione più semplice ansible sulla logica di come funziona? Sono sicuro che questo aiuterà molti altri principianti.

Ecco il mio umile tentativo di spiegare il concetto ai neofiti di tutto il mondo: (una versione con codice colore anche sul mio blog)

Un sacco di gente corre verso una cabina telefonica solitaria (senza cellulari) per parlare con i propri cari. La prima persona a prendere la maniglia della cabina, è quella a cui è permesso usare il telefono. Deve tenersi aggrappato al manico della porta finché usa il telefono, altrimenti qualcun altro prenderà la maniglia, lo butterà fuori e parlerà con sua moglie 🙂 Non esiste un sistema di code in quanto tale. Quando la persona termina la sua chiamata, esce dalla cabina e lascia la maniglia della porta, la persona successiva a prendere la maniglia della porta sarà autorizzata a utilizzare il telefono.

Una discussione è: ogni persona
Il mutex è: la maniglia della porta
Il lucchetto è: la mano della persona
La risorsa è: il telefono

Qualsiasi thread che deve eseguire alcune linee di codice che non dovrebbero essere modificate da altri thread contemporaneamente (usando il telefono per parlare con sua moglie), deve prima acquisire un blocco su un mutex (stringendo la maniglia della cabina della cabina ). Solo allora un thread sarà in grado di eseguire quelle linee di codice (effettuando la telefonata).

Una volta che il thread ha eseguito quel codice, dovrebbe rilasciare il blocco sul mutex in modo che un altro thread possa acquisire un blocco sul mutex (altre persone possano accedere alla cabina telefonica).

[ Il concetto di avere un mutex è un po ‘assurdo quando si considera l’accesso esclusivo al mondo reale, ma nel mondo della programmazione credo che non ci fosse altro modo per far vedere agli altri thread che un thread stava già eseguendo alcune linee di codice. Esistono concetti di mutex ricorsivi, ecc., Ma questo esempio voleva solo mostrarti il ​​concetto di base. Spero che l’esempio ti dia un’immagine chiara del concetto. ]

Con la filettatura C ++ 11:

 #include  #include  #include  std::mutex m;//you can use std::lock_guard if you want to be exception safe int i = 0; void makeACallFromPhoneBooth() { m.lock();//man gets a hold of the phone booth door and locks it. The other men wait outside //man happily talks to his wife from now.... std::cout < < i << " Hello Wife" << std::endl; i++;//no other thread can access variable i until m.unlock() is called //...until now, with no interruption from other men m.unlock();//man lets go of the door handle and unlocks the door } int main() { //This is the main crowd of people uninterested in making a phone call //man1 leaves the crowd to go to the phone booth std::thread man1(makeACallFromPhoneBooth); //Although man2 appears to start second, there's a good chance he might //reach the phone booth before man1 std::thread man2(makeACallFromPhoneBooth); //And hey, man3 also joined the race to the booth std::thread man3(makeACallFromPhoneBooth); man1.join();//man1 finished his phone call and joins the crowd man2.join();//man2 finished his phone call and joins the crowd man3.join();//man3 finished his phone call and joins the crowd return 0; } 

Compilare ed eseguire usando g++ -std=c++0x -pthread -o thread thread.cpp;./thread

Invece di usare esplicitamente lock e unlock , puoi usare parentesi quadre come mostrato qui , se stai usando un lock con scope per il vantaggio che fornisce . Tuttavia, le serrature scoperte hanno un leggero sovraccarico.

Con TBB: Avrai bisogno di TBB per eseguire il programma seguente, ma l'intento di pubblicare un codice TBB è che tu capisca la sequenza di blocco e sblocco solo guardando il codice semplice (potrebbe aver mostrato il blocco dell'ambito non utilizzando l'acquisizione e rilascio - che è anche eccezionalmente sicuro -, ma questo è più chiaro).

 #include  #include "/tbb/mutex.h" #include "/tbb/tbb_thread.h" using namespace tbb; typedef mutex myMutex; static myMutex sm; int i = 0; void someFunction() { //Note: Since a scoped lock is used below, you should know that you //can specify a scope for the mutex using curly brackets, instead of //using lock.acquire() and lock.release(). The lock will automatically //get released when program control goes beyond the scope. myMutex::scoped_lock lock;//create a lock lock.acquire(sm);//Method acquire waits until it can acquire a lock on the mutex //***only one thread can access the lines from here...*** ++i;//incrementing i is safe (only one thread can execute the code in this scope) because the mutex locked above protects all lines of code until the lock release. sleep(1);//simply creating a delay to show that no other thread can increment i until release() is executed std::cout< <"In someFunction "< 

Si noti che tbb_thread.h è deprecato. La sostituzione è mostrata qui .

Mentre un mutex può essere usato per risolvere altri problemi, la ragione principale che esiste è quella di fornire l’esclusione reciproca e quindi di risolvere quella che è nota come condizione di competizione. Quando due (o più) thread o processi stanno tentando di accedere contemporaneamente alla stessa variabile, abbiamo il potenziale per una condizione di competizione. Considera il seguente codice

 //somewhere long ago, we have i declared as int void my_concurrently_called_function() { i++; } 

Gli interni di questa funzione sembrano così semplici. È solo una dichiarazione. Tuttavia, un tipico equivalente al linguaggio pseudo-assembly potrebbe essere:

 load i from memory into a register add 1 to i store i back into memory 

Poiché le istruzioni equivalenti in linguaggio assembly sono tutte necessarie per eseguire l’operazione di incremento su i, diciamo che l’incremento di i è un’operazione non atmoica. Un’operazione atomica è quella che può essere completata sull’hardware con una garanzia di non essere interrotta una volta che l’esecuzione dell’istruzione è iniziata. Incrementare i consiste in una catena di 3 istruzioni atomiche. In un sistema concorrente in cui diversi thread chiamano la funzione, i problemi sorgono quando un thread legge o scrive nel momento sbagliato. Immagina di avere due thread in esecuzione simultanea e uno chiama la funzione immediatamente dopo l’altro. Diciamo anche che abbiamo inizializzato a 0. Supponiamo anche che abbiamo un sacco di registri e che i due thread stanno usando registri completamente diversi, quindi non ci saranno collisioni. Il momento effettivo di questi eventi può essere:

 thread 1 load 0 into register from memory corresponding to i //register is currently 0 thread 1 add 1 to a register //register is now 1, but not memory is 0 thread 2 load 0 into register from memory corresponding to i thread 2 add 1 to a register //register is now 1, but not memory is 0 thread 1 write register to memory //memory is now 1 thread 2 write register to memory //memory is now 1 

Quello che è successo è che abbiamo due thread che incrementano i simultaneamente, la nostra funzione viene chiamata due volte, ma il risultato è incoerente con questo fatto. Sembra che la funzione sia stata chiamata solo una volta. Questo perché l’atomicità è “rotta” a livello della macchina, il che significa che i thread possono interrompersi a vicenda o lavorare insieme nei momentjs sbagliati.

Abbiamo bisogno di un meccanismo per risolvere questo. Dobbiamo imporre alcuni ordini alle istruzioni di cui sopra. Un meccanismo comune è quello di bloccare tutti i thread tranne uno. Il mutex Pthread usa questo meccanismo.

Qualsiasi thread che deve eseguire alcune linee di codice che possono modificare in modo non sicuro i valori condivisi da altri thread allo stesso tempo (usando il telefono per parlare con la moglie), dovrebbe prima essere fatto acquisire un blocco su un mutex. In questo modo, qualsiasi thread che richiede l’accesso ai dati condivisi deve passare attraverso il blocco mutex. Solo allora un thread sarà in grado di eseguire il codice. Questa sezione di codice è chiamata sezione critica.

Una volta che il thread ha eseguito la sezione critica, dovrebbe rilasciare il blocco sul mutex in modo che un altro thread possa acquisire un blocco sul mutex.

Il concetto di avere un mutex sembra un po ‘strano quando si considerano gli umani che cercano l’accesso esclusivo a oggetti fisici reali, ma quando si programma, dobbiamo essere intenzionali. Thread e processi contemporanei non hanno l’educazione sociale e culturale che facciamo, quindi dobbiamo costringerli a condividere i dati in modo piacevole.

Quindi, tecnicamente parlando, come funziona un mutex? Non soffre delle stesse condizioni di gara di cui abbiamo parlato prima? Pthread_mutex_lock () non è un po ‘più complesso di un semplice incremento di una variabile?

Tecnicamente parlando, abbiamo bisogno di un supporto hardware per aiutarci. I progettisti dell’hardware ci danno istruzioni per le macchine che fanno più di una cosa, ma sono garantite per essere atomiche. Un classico esempio di tale istruzione è il test-and-set (TAS). Quando proviamo ad acquisire un lock su una risorsa, potremmo usare TAS per verificare se un valore in memoria è 0. Se lo è, sarebbe il nostro segnale che la risorsa è in uso e non facciamo nulla (o più accuratamente , attendiamo con un meccanismo: un mutex pthreads ci metterà in una coda speciale nel sistema operativo e ci notificherà quando la risorsa sarà disponibile.I sistemi di Dumber potrebbero richiedere un ciclo di spin ristretto, testando ripetutamente la condizione) . Se il valore in memoria non è 0, TAS imposta la posizione su un valore diverso da 0 senza utilizzare altre istruzioni. È come combinare due istruzioni di assemblaggio in 1 per darci l’atomicità. Pertanto, testare e modificare il valore (se la modifica è appropriata) non può essere interrotto una volta iniziato. Possiamo build mutex oltre a queste istruzioni.

Nota: alcune sezioni potrebbero apparire simili a una risposta precedente. Ho accettato il suo invito a modificare, preferiva l’originale, quindi sto mantenendo quello che avevo, che è infuso da un po ‘della sua verbosità.

Il tutorial sui thread migliori che conosco è qui:

https://computing.llnl.gov/tutorials/pthreads/

Mi piace che sia scritto sull’API, piuttosto che su una particolare implementazione, e fornisce alcuni semplici esempi per aiutarti a capire la sincronizzazione.

Recentemente sono incappato in questo post e penso che abbia bisogno di una soluzione aggiornata per il mutex c ++ 11 della libreria standard (vale a dire std :: mutex).

Ho incollato del codice qui sotto (i miei primi passi con un mutex – ho imparato la concorrenza su win32 con HANDLE, SetEvent, WaitForMultipleObjects ecc.).

Dato che è il mio primo tentativo con std :: mutex e amici, mi piacerebbe vedere commenti, suggerimenti e miglioramenti!

 #include  #include  #include  #include  #include  #include  #include  int _tmain(int argc, _TCHAR* argv[]) { // these vars are shared among the following threads std::queue nNumbers; std::mutex mtxQueue; std::condition_variable cvQueue; bool m_bQueueLocked = false; std::mutex mtxQuit; std::condition_variable cvQuit; bool m_bQuit = false; std::thread thrQuit( [&]() { using namespace std; this_thread::sleep_for(chrono::seconds(5)); // set event by setting the bool variable to true // then notifying via the condition variable m_bQuit = true; cvQuit.notify_all(); } ); std::thread thrProducer( [&]() { using namespace std; int nNum = 13; unique_lock lock( mtxQuit ); while ( ! m_bQuit ) { while( cvQuit.wait_for( lock, chrono::milliseconds(75) ) == cv_status::timeout ) { nNum = nNum + 13 / 2; unique_lock qLock(mtxQueue); cout < < "Produced: " << nNum << "\n"; nNumbers.push( nNum ); } } } ); std::thread thrConsumer( [&]() { using namespace std; unique_lock lock(mtxQuit); while( cvQuit.wait_for(lock, chrono::milliseconds(150)) == cv_status::timeout ) { unique_lock qLock(mtxQueue); if( nNumbers.size() > 0 ) { cout < < "Consumed: " << nNumbers.front() << "\n"; nNumbers.pop(); } } } ); thrQuit.join(); thrProducer.join(); thrConsumer.join(); return 0; } 

La funzione pthread_mutex_lock() o acquisisce il mutex per il thread chiamante o blocca il thread fino a che il mutex non può essere acquisito. Il relativo pthread_mutex_unlock() rilascia il mutex.

Pensa al mutex come a una coda; ogni thread che tenta di acquisire il mutex verrà posizionato alla fine della coda. Quando un thread rilascia il mutex, il thread successivo nella coda si distriggers e ora è in esecuzione.

Una sezione critica si riferisce a una regione di codice in cui è ansible non determinismo. Spesso questo perché più thread stanno tentando di accedere a una variabile condivisa. La sezione critica non è sicura fino a quando non sarà in atto una sorta di sincronizzazione. Un blocco mutex è una forma di sincronizzazione.

Si suppone di controllare la variabile mutex prima di utilizzare l’area protetta dal mutex. Quindi il tuo pthread_mutex_lock () potrebbe (a seconda dell’implementazione) attendere che mutex1 sia rilasciato o restituire un valore che indica che il blocco non può essere ottenuto se qualcun altro l’ha già bloccato.

Mutex è davvero solo un semaforo semplificato. Se leggi su di loro e li capisci, capisci i mutex. Ci sono diverse domande riguardanti mutex e semafori in SO. Differenza tra semaforo e mutex binari , Quando dovremmo usare mutex e quando dovremmo usare il semaforo e così via. L’esempio di toilette nel primo link è un buon esempio come si può pensare. Tutto il codice deve verificare se la chiave è disponibile e se lo è, la riserva. Si noti che in realtà non si riserva il bagno in sé, ma la chiave.

ESEMPIO SEMPLICE ::

 sem_t m; sem_init(&m, 0, 0); // initialize semaphore to 0 sem_wait(&m); // critical section here sem_post(&m); 

Riferimento: http://pages.cs.wisc.edu/~remzi/Classes/537/Fall2008/Notes/threads-semaphores.txt

Per chi cerca l’esempio di mutex shortex:

 #include  using namespace std; int main() { mutex m; m.lock(); // do thread-safe stuff m.unlock(); return 0; }