std::mutex în C++

Încuietoare pentru date comune: un singur fir de execuție o poate ține la un moment dat.

Sintaxă
#include <mutex>
std::mutex m;
void lock();
void unlock();
bool try_lock();

Ce face std::mutex

Când mai multe fire modifică aceleași date, trebuie să se ia la rând. Un std::mutex („mutual exclusion”) garantează asta: m.lock() așteaptă până când mutexul este liber și îl ocupă, m.unlock() îl eliberează. Codul dintre ele (secțiunea critică) rulează pe un singur fir odată.

Nu apela lock/unlock manual: dacă între ele apare o excepție sau un return, mutexul rămâne blocat pentru totdeauna. Folosește std::lock_guard, std::unique_lock sau std::scoped_lock, care îl eliberează automat.

try_lock() încearcă să ocupe mutexul fără să aștepte și returnează false dacă este deja ocupat.

Ce returnează

try_lock() returnează true dacă a reușit. lock() și unlock() nu returnează nimic.

Exemple

Exemplul 1: Un contor modificat de 4 fire

Fără mutex, totalul ar ieși de multe ori mai mic de 40000 (și ar fi comportament nedefinit). Cu mutex, este mereu corect.

#include <mutex>
#include <print>
#include <thread>
#include <vector>

int main() {
    int vizite = 0;
    std::mutex m;

    std::vector<std::thread> fire;
    for (int i = 0; i < 4; ++i) {
        fire.emplace_back([&] {
            for (int j = 0; j < 10'000; ++j) {
                std::lock_guard<std::mutex> blocare(m);
                ++vizite;
            }
        });
    }
    for (auto& t : fire) t.join();

    std::println("Vizite: {}", vizite);
}

Rezultat

Vizite: 40000

Exemplul 2: try_lock

#include <mutex>
#include <print>

int main() {
    std::mutex m;
    m.lock();
    std::println("a doua încercare reușește: {}", m.try_lock());
    m.unlock();

    if (m.try_lock()) {
        std::println("mutex liber, l-am ocupat");
        m.unlock();
    }
}

Rezultat

a doua încercare reușește: false
mutex liber, l-am ocupat

Greșeli frecvente

  • Uiți unlock() pe o ramură (de exemplu un return timpuriu). Toate celelalte fire rămân blocate la lock(). Folosește std::lock_guard.
  • Apelezi lock() de două ori pe același fir. std::mutex nu este recursiv: firul se blochează singur (comportament nedefinit).
  • Două fire ocupă două mutexuri în ordine inversă (A apoi B, respectiv B apoi A). Se pot bloca reciproc (deadlock); folosește std::scoped_lock pentru amândouă.

Comenzi înrudite

  • std::lock_guard: Ocupă un mutex la creare și îl eliberează automat la ieșirea din bloc (RAII).
  • std::unique_lock: Blocare de mutex flexibilă: poate fi eliberată și reluată, amânată, mutată; necesară pentru condition_variable.
  • std::scoped_lock: Ocupă unul sau mai multe mutexuri deodată, fără risc de blocaj reciproc, și le eliberează automat.
  • std::atomic: Variabilă care poate fi citită și modificată sigur de mai multe fire, fără mutex.
  • std::thread: Pornește o funcție pe un fir de execuție separat, care rulează în paralel cu restul programului.