In C++ moderno (a partire da C++11) esiste un ricco strumento per lavorare con il multithreading: std::thread, mutex e vari oggetti wrapper per gestire l'accesso alle risorse. Un'interazione sicura tra i thread richiede la comprensione dei seguenti componenti:
Per evitare race condition, si raccomanda di usare sempre wrapper RAII quando possibile. Ricorda che tentare di acquisire nuovamente lo stesso mutex porterà a deadlock (se non è un mutex ricorsivo).
Esempio di lavoro corretto:
#include <iostream> #include <mutex> #include <thread> std::mutex mtx; void printThreadSafe(int id) { std::lock_guard<std::mutex> lock(mtx); std::cout << "Thread " << id << std::endl; } int main() { std::thread t1(printThreadSafe, 1); std::thread t2(printThreadSafe, 2); t1.join(); t2.join(); }
È possibile utilizzare std::lock_guardstd::mutex per implementare la sincronizzazione condizionale con std::condition_variable?
Risposta: No! Per lavorare con std::condition_variable è assolutamente necessario std::unique_lockstd::mutex, perché lock_guard non fornisce metodi per controllare il blocco (ad esempio, rilasciare temporaneamente — unlock — e poi acquisire di nuovo), necessari quando si attende una notifica.
Esempio:
std::mutex mtx; std::condition_variable cv; bool ready = false; void waitForEvent() { std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, []{return ready;}); // Ulteriore elaborazione }
Storia
Nel progetto si utilizzava un lavoro manuale con mtx.lock()/mtx.unlock(). Quando si sollevava un'eccezione tra lock/unlock, il mutex rimaneva bloccato — molti thread "si bloccavano". Dopo la sostituzione con RAII (
std::lock_guard), il problema è scomparso.
Storia
Nella notifica tramite std::condition_variable invece di std::unique_lock si utilizzava std::lock_guard. Di conseguenza, il programma non si compilava su nuovi compilatori, mentre su uno vecchio si verificava un'asserzione runtime a causa di un'API incoerente.
Storia
Nel codice veniva chiamato unlock() su std::lock_guard per "controllare manualmente" l'accesso al mutex. Questo portava a comportamenti indefiniti su diversi compilatori, a volte al crash dell'applicazione. È stato compreso che lock_guard non supporta il rilascio manuale, era necessaria la migrazione a unique_lock.