Linguaggio C++

Libreria Mutex

Introduzione alla libreria mutex del C++: sincronizzazione dei thread, uso di mutex, lock_guard, unique_lock, rischi e laboratorio guidato.

Quando più thread accedono agli stessi dati, possono verificarsi problemi di concorrenza come:

  • letture/scritture corrotte
  • condizioni di gara (race condition)
  • risultati imprevedibili

La libreria <mutex> fornisce strumenti per proteggere sezioni di codice in cui accedono più thread contemporaneamente.


1️⃣ Inclusione della libreria


#include <mutex>
using namespace std;

2️⃣ Il problema: Race Condition


int counter = 0;

void incrementa() {
    for(int i=0; i<100000; i++)
        counter++;
}

Se due thread eseguono questa funzione, il risultato NON sarà 200000, perché entrambi modificano la stessa variabile contemporaneamente.


3️⃣ mutex: blocco manuale

Un mutex permette di bloccare una sezione del codice per renderla sicura.


mutex m;

void incrementa() {
    m.lock();
    counter++;
    m.unlock();
}

Ora solo un thread alla volta può entrare nella sezione protetta.


4️⃣ lock_guard – gestione automatica

Il modo consigliato per usare un mutex è con lock_guard.


mutex m;

void incrementa() {
    lock_guard lock(m);  // si sblocca automaticamente
    counter++;
}

✔ Sicuro ✔ Niente unlock mancante ✔ Niente eccezioni per errore


5️⃣ unique_lock – più flessibilità

Permette lock e unlock manuale.


mutex m;

void lavoro() {
    unique_lock lock(m);
    // codice protetto
    
    lock.unlock();  // rilascio anticipato
}

6️⃣ try_lock(): tentativo senza blocco


if(m.try_lock()) {
    cout << "Mutex preso!";
    m.unlock();
}
else {
    cout << "Mutex occupato!";
}

7️⃣ Deadlock: il pericolo dei mutex

Si verifica quando due thread aspettano all infinito uno il mutex dell altro.


// NON FARLO!
lock(m1);
lock(m2);

8️⃣ Come evitare deadlock

  • Usare sempre la stessa ordine di blocco
  • Preferire lock_guard e unique_lock
  • Usare std::lock() per bloccare più mutex insieme

9️⃣ Esempio completo


mutex m;
int counter = 0;

void incrementa() {
    for(int i=0; i<100000; i++) {
        lock_guard lock(m);
        counter++;
    }
}

int main() {
    thread t1(incrementa);
    thread t2(incrementa);

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

    cout << counter;
}
LABORATORIO

Uso di mutex e sezioni critiche

In questo laboratorio imparerai a proteggere sezioni di codice condivise tra thread, comprendere le race condition e usare correttamente i mutex con lock_guard e unique_lock.


1 Creare una race condition

Implementa due thread che incrementano la stessa variabile senza mutex.


int counter = 0;

void inc() {
    for(int i=0; i<100000; i++)
        counter++;
}

2 Aggiungi un mutex per proteggere il contatore


mutex m;

void inc() {
    for(int i=0; i<100000; i++) {
        lock_guard lock(m);
        counter++;
    }
}

3 Prova unique_lock


unique_lock lock(m);
cout << "Sezione protetta";

4 Testa try_lock


if(m.try_lock()) {
    cout << "Preso!";
    m.unlock();
}
else {
    cout << "Occupato!";
}

5 Esercizi

  • Proteggi una lista condivisa tra più thread che inseriscono elementi.
  • Simula due bancomat che prelevano dal medesimo conto.
  • Crea un logger multithread che scrive su file con mutex.
  • Evita deadlock usando sempre lo stesso ordine di blocco per 2 mutex.