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.
- Libreria
<mutex> - lock_guard e unique_lock
- Thread multipli
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.