ProgrammazioneSviluppatore C++ multithreading/server

Quali sono i dettagli da considerare quando si lavora con la multithreading in C++? Descrivi la differenza tra std::mutex, std::lock_guard, std::unique_lock e fornisci un esempio di lavoro sicuro con i thread.

Supera i colloqui con l'assistente IA Hintsage

Risposta

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:

  • std::mutex — il principale primitivo di blocco per sincronizzare l'accesso. Richiede esplicito lock/unlock.
  • std::lock_guard — un wrapper RAII attorno al mutex, che acquisisce e libera automaticamente il mutex all'ingresso/uscita dall'ambito.
  • std::unique_lock — un wrapper RAII più versatile, consente di bloccare/sbloccare manualmente e di trasferire la proprietà del mutex tra thread e utilizzare condition_variable.

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(); }

Domanda trabocchetto

È 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 }

Esempi di errori reali a causa della mancanza di conoscenza dei dettagli del tema


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.