2016-02-07 16 views
8

Ho raggiunto un punto nel mio progetto che richiede la comunicazione tra thread su risorse a cui può essere scritto molto bene, quindi la sincronizzazione è un must. Tuttavia, in realtà non capisco la sincronizzazione con qualcosa di diverso dal livello base.std: esempio lock_guard, spiegazione del motivo per cui funziona

consideri l'ultimo esempio in questo link: http://www.bogotobogo.com/cplusplus/C11/7_C11_Thread_Sharing_Memory.php

#include <iostream> 
#include <thread> 
#include <list> 
#include <algorithm> 
#include <mutex> 

using namespace std; 

// a global variable 
std::list<int>myList; 

// a global instance of std::mutex to protect global variable 
std::mutex myMutex; 

void addToList(int max, int interval) 
{ 
    // the access to this function is mutually exclusive 
    std::lock_guard<std::mutex> guard(myMutex); 
    for (int i = 0; i < max; i++) { 
     if((i % interval) == 0) myList.push_back(i); 
    } 
} 

void printList() 
{ 
    // the access to this function is mutually exclusive 
    std::lock_guard<std::mutex> guard(myMutex); 
    for (auto itr = myList.begin(), end_itr = myList.end(); itr != end_itr; ++itr) { 
     cout << *itr << ","; 
    } 
} 

int main() 
{ 
    int max = 100; 

    std::thread t1(addToList, max, 1); 
    std::thread t2(addToList, max, 10); 
    std::thread t3(printList); 

    t1.join(); 
    t2.join(); 
    t3.join(); 

    return 0; 
} 

L'esempio dimostra come tre fili, due scrittori e un lettore, accede a una risorsa comune (lista).

Vengono utilizzate due funzioni globali: una utilizzata dai due thread di writer e una utilizzata dal thread di lettura. Entrambe le funzioni utilizzano un lock_guard per bloccare la stessa risorsa, la lista.

Ora ecco cosa non riesco a comprendere: il lettore utilizza un blocco in un ambito diverso rispetto ai due thread di writer, ma blocca ancora la stessa risorsa. Come può funzionare? La mia comprensione limitata dei mutex si presta bene alla funzione di scrittura, ci sono due thread che usano la stessa identica funzione. Posso capire che un controllo viene effettuato mentre stai per entrare nell'area protetta, e se qualcun altro è già dentro, aspetti.

Ma quando l'ambito è diverso? Ciò indicherebbe che esiste un meccanismo più potente del processo stesso, una sorta di ambiente di runtime che blocca l'esecuzione del thread "tardivo". Ma pensavo che non ci fossero cose del genere in C++. Quindi sono in perdita.

Cosa succede esattamente sotto il cofano qui?

+1

Vorrei aggiungere che il blocco mutex è [atomico] (http://stackoverflow.com/questions/15054086/what-does-atomic-mean-in-programming). Spero che questo ti aiuti. – Incomputable

risposta

3

myMutex è globale, che è quello che viene utilizzato per proteggere myList. guard(myMutex) semplicemente impegna il blocco e l'uscita dal blocco ne provoca la distruzione, disinnestando la serratura. guard è solo un modo conveniente per attivare e disattivare la serratura.

Con quello fuori strada, mutex non protegge alcun dato. Fornisce solo un modo per proteggere i dati. È il modello di progettazione che protegge i dati. Quindi se scrivo la mia funzione per modificare l'elenco come di seguito, il mutex non può proteggerlo.

void addToListUnsafe(int max, int interval) 
{ 
    for (int i = 0; i < max; i++) { 
     if((i % interval) == 0) myList.push_back(i); 
    } 
} 

La serratura funziona solo se tutte le parti di codice che devono accedere ai dati impegnano la serratura prima di accedere e sganciarsi dopo hanno finito. Questo design-pattern di coinvolgente e dis-azionato il dispositivo prima e dopo ogni accesso è ciò che protegge i dati (myList nel tuo caso)

Ora si dovrebbe chiedersi, perché usare mutex a tutti, e perché no, ad esempio, un bool. E sì, puoi, ma dovrai assicurarti che la variabile bool mostrerà alcune caratteristiche incluso ma non limitato alla lista qui sotto.

  1. Non essere memorizzato nella cache (volatile) su più thread.
  2. La lettura e la scrittura saranno operazioni atomiche.
  3. Il blocco può gestire la situazione in cui sono presenti più pipeline di esecuzione (core logici, ecc.).

Esistono diversi synchronization meccanismi che forniscono "miglior bloccaggio" (attraverso processi contro tutti thread, processori multipli contro, singolo processore, ecc) al costo di "performance lenta", così si dovrebbe sempre scegliere un meccanismo di bloccaggio che è abbastanza per la tua situazione.

2

Questo è esattamente ciò che fa un lucchetto. Quando un thread prende il lock, indipendentemente da dove nel codice lo fa, deve aspettare il suo turno se un altro thread tiene il lock. Quando un thread rilascia un blocco, indipendentemente da dove nel codice lo fa, un altro thread può acquisire quel blocco.

I blocchi proteggono i dati, non il codice. Lo fanno garantendo che tutto il codice che accede ai dati protetti lo fa mentre tiene il blocco, escludendo altri thread da qualsiasi codice che potrebbe accedere agli stessi dati.

8

Diamo uno sguardo alla linea in questione:

std::lock_guard<std::mutex> guard(myMutex); 

Si noti che i riferimenti lock_guard il globale mutex myMutex. Cioè, lo stesso mutex per tutti e tre i thread. Nei lock_guard fa è essenzialmente questo:

  • Al costruzione, blocca myMutex e mantiene un riferimento ad esso.
  • In caso di distruzione (vale a dire quando il raggio di guardia è lasciato), sblocca myMutex.

Il mutex è sempre lo stesso, non ha nulla a che fare con lo scope. Il punto di lock_guard è solo per rendere più facile bloccare e sbloccare il mutex. Ad esempio, se si manualmente lock/unlock, ma la propria funzione genera un'eccezione da qualche parte nel mezzo, non raggiungerà mai l'istruzione unlock. Quindi, facendo in modo manuale si si deve assicurarsi che il mutex è sempre sbloccato.D'altra parte, l'oggetto lock_guard viene distrutto automaticamente ogni volta che si esce dalla funzione, indipendentemente da come viene abbandonato.

+1

Hai ragione, ma temo di essere stato incompreso. Ciò che intendo in realtà è come funziona a basso livello. Un pezzo di dati è solo la memoria impostata su qualcosa. Se vuoi proteggere quei dati, devi incapsularlo. Non sembra essere qualcosa di simile al lavoro qui, puoi facilmente bloccare un semplice int nello stesso modo. Quindi, qual è il meccanismo di protezione che il blocco rappresenta effettivamente implementato? Poiché lo scope è irrilevante, qualcos'altro deve intersecare il thread in ritardo, chiudendolo e risvegliandolo una volta che la risorsa è di nuovo libera. Prendimi? – Deviatore

+0

@Daviatore: si chiama scheduler del sistema operativo – MikeMB

+0

Non si tratta di incapsulamento. Il mutex è essenzialmente una risorsa che si acquisisce quando viene chiamato 'lock()'. Se la risorsa non può essere acquisita (perché un altro thread lo trattiene), ciò che accade dipende dall'implementazione concreta; o [algoritmi di mutua esclusione] (https://en.wikipedia.org/wiki/Mutual_exclusion#Enforcing_mutual_exclusion) vengono utilizzati, oppure il sistema operativo lo gestisce, mettendo il thread su una coda in attesa (si veda ad esempio questo [tutorial sui thread POSIX Programmazione] (https://computing.llnl.gov/tutorials/pthreads/#Mutexes) per come i sistemi UNIX/Linux lo fanno). – mindriot

Problemi correlati