programowanieProgramista C++ wielowątkowy/serwerowy

Jakie niuanse należy uwzględnić przy pracy z wielowątkowością w C++? Opisz różnicę między std::mutex, std::lock_guard, std::unique_lock i podaj przykład bezpiecznej pracy z wątkami.

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź

W nowoczesnym C++ (od C++11) istnieje bogaty zestaw narzędzi do pracy z wielowątkowością: std::thread, mutexy i różne obiekty opakowujące do zarządzania dostępem do zasobów. Bezpieczna interakcja między wątkami wymaga zrozumienia następujących komponentów:

  • std::mutex — podstawowy prymityw blokady do synchronizacji dostępu. Wymaga jawnego blokowania/odblokowywania.
  • std::lock_guard — opakowanie RAII nad mutexem, które automatycznie przejmuje i zwalnia mutex przy wejściu/wyjściu z zakresu widoczności.
  • std::unique_lock — bardziej uniwersalne opakowanie RAII, pozwala na ręczne blokowanie/odblokowywanie, a także przenoszenie własności mutexu między wątkami i korzystanie z condition_variable.

Aby uniknąć wyścigów, zawsze używaj opakowań RAII tam, gdzie to możliwe. Pamiętaj, że próba ponownego przejęcia tego samego mutexu doprowadzi do deadlocku (o ile nie jest to rekurencyjny mutex).

Przykład poprawnej pracy:

#include <iostream> #include <mutex> #include <thread> std::mutex mtx; void printThreadSafe(int id) { std::lock_guard<std::mutex> lock(mtx); std::cout << "Wątek " << id << std::endl; } int main() { std::thread t1(printThreadSafe, 1); std::thread t2(printThreadSafe, 2); t1.join(); t2.join(); }

Pytanie z podchwytliwością

Czy można używać std::lock_guardstd::mutex do realizacji warunkowej synchronizacji z std::condition_variable?

Odpowiedź: Nie! Do pracy z std::condition_variable koniecznie potrzebny jest std::unique_lockstd::mutex, ponieważ lock_guard nie udostępnia metod do kontrolowania blokady (na przykład, tymczasowego zwalniania — unlock — a następnie ponownego przejmowania), które są niezbędne podczas oczekiwania na powiadomienie.

Przykład:

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

Przykłady rzeczywistych błędów z powodu braku znajomości niuansów tematu


Historia

W projekcie stosowano ręczną obsługę mtx.lock()/mtx.unlock(). Przy wystąpieniu wyjątku między lock/unlock, mutex pozostawał zablokowany — wiele wątków "zawieszało" się. Po zamianie na RAII (std::lock_guard) problem zniknął.


Historia

Przy powiadamianiu przez std::condition_variable zamiast std::unique_lock używano std::lock_guard. W rezultacie, program nie kompilował się na nowych kompilatorach, a na starym — występował assert czasu wykonywania z powodu niespójnego API.


Historia

W kodzie wywoływano unlock() na std::lock_guard dla "ręcznej" kontroli dostępu do mutexu. To prowadziło do nieokreślonego zachowania na różnych kompilatorach, czasami do awarii aplikacji. Zrozumiano, że lock_guard nie wspiera ręcznego zwalniania, potrzebna była migracja do unique_lock.