ProgrammationDéveloppeur C++ multithread/serveur

Quels points doivent être pris en compte lors de l'utilisation de la multithreading en C++ ? Décrivez la différence entre std::mutex, std::lock_guard, std::unique_lock et donnez un exemple de travail sûr avec des threads.

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse

En C++ moderne (à partir de C++11), il existe un riche ensemble d'outils pour travailler avec la multithreading : std::thread, mutex et divers objets d'encapsulation pour gérer l'accès aux ressources. Une interaction sécurisée entre les threads nécessite la compréhension des composants suivants :

  • std::mutex — le principal primitif de verrouillage pour synchroniser l'accès. Nécessite un verrouillage/déverrouillage explicite.
  • std::lock_guard — une encapsulation RAII sur le mutex, qui capture et libère automatiquement le mutex à l'entrée/sortie de la portée.
  • std::unique_lock — une encapsulation RAII plus universelle, permet de verrouiller/déverrouiller manuellement, ainsi que de déplacer la propriété du mutex entre les threads et d'utiliser condition_variable.

Pour éviter les courses, utilisez toujours des encapsulations RAII là où c'est possible. Notez que tenter de capturer à nouveau le même mutex entraînera un deadlock (à moins qu'il ne s'agisse d'un mutex récursif).

Exemple de travail correct :

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

Question piège

Peut-on utiliser std::lock_guardstd::mutex pour implémenter la synchronisation conditionnelle avec std::condition_variable ?

Réponse : Non ! Pour travailler avec std::condition_variable, il est absolument nécessaire d'utiliser std::unique_lockstd::mutex, car lock_guard ne fournit pas de méthodes pour contrôler le verrouillage (par exemple, relâcher temporairement — unlock — puis le reprendre), qui sont nécessaires lors de l'attente d'une notification.

Exemple :

std::mutex mtx; std::condition_variable cv; bool ready = false; void waitForEvent() { std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, []{return ready;}); // Traitement ultérieur }

Exemples d'erreurs réelles dues à une méconnaissance des subtilités du sujet


Histoire

Le projet utilisait un travail manuel avec mtx.lock()/mtx.unlock(). Lorsqu'une exception était levée entre lock/unlock, le mutex restait verrouillé — plusieurs threads "accrochaient". Après avoir remplacé par RAII (std::lock_guard), le problème a disparu.


Histoire

Lors de la notification via std::condition_variable, std::lock_guard était utilisé au lieu de std::unique_lock. En conséquence, le programme ne se compilait pas sur de nouveaux compilateurs, tandis que sur l'ancien — une assertion d'exécution se produisait en raison d'une API incohérente.


Histoire

Dans le code, unlock() était appelé sur std::lock_guard pour un contrôle "manuel" de l'accès au mutex. Cela a conduit à un comportement indéfini sur différents compilateurs, parfois à un plantage de l'application. Il a été compris que lock_guard ne prend pas en charge le relâchement manuel, un passage à unique_lock était nécessaire.