Skip to content

🔒 Synchronization — mutex, lock_guard, unique_lock

Mutex (Mutual Exclusion) đảm bảo chỉ 1 thread có thể truy cập critical section tại một thời điểm.

std::mutex Basics

Cú pháp cơ bản

cpp
#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;  // Mutex object
int counter = 0;

void increment() {
    for (int i = 0; i < 100000; ++i) {
        mtx.lock();    // 🔒 Khóa — chờ nếu thread khác đang hold
        counter++;     // Critical section — an toàn
        mtx.unlock();  // 🔓 Mở khóa
    }
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);
    
    t1.join();
    t2.join();
    
    std::cout << "Counter: " << counter << std::endl;  // ✅ 200000
    return 0;
}

Mutex Mental Model

┌─────────────────────────────────────────────────────────────────┐
│                    MUTEX VISUALIZATION                           │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   Thread 1                    Thread 2                          │
│   ─────────                   ─────────                         │
│                                                                 │
│   mtx.lock();                                                   │
│   ┌──────────────┐                                              │
│   │   counter++  │            mtx.lock(); // ⏳ BLOCKED!        │
│   │  (trong)     │                        // Đợi Thread 1       │
│   └──────────────┘                                              │
│   mtx.unlock();                                                 │
│                               // Được chạy tiếp                  │
│                               ┌──────────────┐                  │
│                               │   counter++  │                  │
│                               └──────────────┘                  │
│                               mtx.unlock();                     │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Vấn đề với manual lock/unlock

Exception Safety

cpp
void dangerous() {
    mtx.lock();
    
    doSomething();  // ⚠️ Nếu throw exception...
    
    mtx.unlock();   // ... dòng này không được gọi!
                    // → DEADLOCK! Mutex bị locked mãi mãi
}

Quên unlock

cpp
void alsoDANGEROUS() {
    mtx.lock();
    
    if (someCondition) {
        return;     // ⚠️ Quên unlock!
    }
    
    mtx.unlock();
}

⚠️ NEVER USE lock()/unlock() DIRECTLY

Luôn dùng RAII wrappers như lock_guard hoặc unique_lock!


std::lock_guard — RAII Wrapper

Cú pháp

cpp
#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;
int counter = 0;

void increment() {
    for (int i = 0; i < 100000; ++i) {
        // ✅ RAII: Tự động lock khi tạo, unlock khi ra scope
        std::lock_guard<std::mutex> lock(mtx);
        counter++;
    }  // 🔓 Tự động unlock ở đây, dù có exception hay không!
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);
    
    t1.join();
    t2.join();
    
    std::cout << "Counter: " << counter << std::endl;  // ✅ 200000
    return 0;
}

C++17: Class Template Argument Deduction (CTAD)

cpp
// C++11/14
std::lock_guard<std::mutex> lock(mtx);

// C++17: Compiler tự deduce template argument
std::lock_guard lock(mtx);

lock_guard Internals

cpp
// Simplified implementation
template<typename Mutex>
class lock_guard {
    Mutex& mutex_;
    
public:
    explicit lock_guard(Mutex& m) : mutex_(m) {
        mutex_.lock();  // Lock trong constructor
    }
    
    ~lock_guard() {
        mutex_.unlock();  // Unlock trong destructor
    }
    
    // Không cho copy
    lock_guard(const lock_guard&) = delete;
    lock_guard& operator=(const lock_guard&) = delete;
};

std::unique_lock — Flexible Locking

unique_lock giống lock_guard nhưng linh hoạt hơn:

Features

Featurelock_guardunique_lock
RAII
Manual unlock
Deferred locking
Try locking
Movable
Condition var

Basic Usage

cpp
#include <mutex>
#include <iostream>

std::mutex mtx;

void demo() {
    std::unique_lock<std::mutex> lock(mtx);
    
    // Có thể unlock sớm
    lock.unlock();
    
    // ... làm việc không cần lock ...
    
    // Lock lại
    lock.lock();
    
    // ... làm việc cần lock ...
    
}  // Tự động unlock nếu đang locked

Deferred Locking

cpp
void deferredDemo() {
    // ❗ std::defer_lock: KHÔNG lock ngay
    std::unique_lock<std::mutex> lock(mtx, std::defer_lock);
    
    // ... chuẩn bị gì đó ...
    
    lock.lock();  // Lock sau
    
    // ... critical section ...
}

Try Locking

cpp
void tryLockDemo() {
    std::unique_lock<std::mutex> lock(mtx, std::try_to_lock);
    
    if (lock.owns_lock()) {
        std::cout << "Got the lock!\n";
        // ... do work ...
    } else {
        std::cout << "Lock is busy, doing something else...\n";
    }
}

Timed Locking

cpp
#include <mutex>
#include <chrono>

std::timed_mutex tmtx;  // Phải dùng timed_mutex

void timedLockDemo() {
    std::unique_lock<std::timed_mutex> lock(tmtx, std::defer_lock);
    
    // Chờ tối đa 100ms
    if (lock.try_lock_for(std::chrono::milliseconds(100))) {
        std::cout << "Got lock within timeout\n";
    } else {
        std::cout << "Timeout!\n";
    }
}

std::scoped_lock (C++17) — Multiple Mutexes

Vấn đề: Lock nhiều mutex

cpp
std::mutex mtx1, mtx2;

void thread1() {
    std::lock_guard<std::mutex> lock1(mtx1);
    std::lock_guard<std::mutex> lock2(mtx2);  // ⚠️ Deadlock risk!
    // ...
}

void thread2() {
    std::lock_guard<std::mutex> lock2(mtx2);
    std::lock_guard<std::mutex> lock1(mtx1);  // ⚠️ Ngược thứ tự!
    // ...
}

Giải pháp C++17: scoped_lock

cpp
#include <mutex>

std::mutex mtx1, mtx2;

void safeMultipleLock() {
    // ✅ Lock cả 2 mutex atomically, không deadlock
    std::scoped_lock lock(mtx1, mtx2);
    
    // ... safe to access resources protected by both mutexes ...
}

Pre-C++17: std::lock()

cpp
void safeMultipleLockOld() {
    // std::lock() locks multiple mutexes without deadlock
    std::lock(mtx1, mtx2);
    
    // adopt_lock: Mutex đã locked, chỉ cần quản lý unlock
    std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);
    std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);
    
    // ... critical section ...
}

Practical Example: Thread-safe Counter Class

cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>

class ThreadSafeCounter {
    mutable std::mutex mutex_;  // mutable để dùng trong const methods
    int value_ = 0;
    
public:
    void increment() {
        std::lock_guard<std::mutex> lock(mutex_);
        ++value_;
    }
    
    void decrement() {
        std::lock_guard<std::mutex> lock(mutex_);
        --value_;
    }
    
    int get() const {
        std::lock_guard<std::mutex> lock(mutex_);
        return value_;
    }
};

int main() {
    ThreadSafeCounter counter;
    std::vector<std::thread> threads;
    
    // 10 threads, mỗi thread increment 10000 lần
    for (int i = 0; i < 10; ++i) {
        threads.emplace_back([&counter]() {
            for (int j = 0; j < 10000; ++j) {
                counter.increment();
            }
        });
    }
    
    for (auto& t : threads) {
        t.join();
    }
    
    std::cout << "Final count: " << counter.get() << std::endl;
    // Output: 100000 (always correct!)
    
    return 0;
}

std::recursive_mutex

Khi cùng một thread cần lock nhiều lần:

cpp
#include <mutex>
#include <iostream>

std::recursive_mutex rmtx;

void recursiveFunction(int depth) {
    std::lock_guard<std::recursive_mutex> lock(rmtx);
    
    std::cout << "Depth: " << depth << std::endl;
    
    if (depth > 0) {
        recursiveFunction(depth - 1);  // ✅ OK với recursive_mutex
    }
}

int main() {
    recursiveFunction(3);
    return 0;
}

⚠️ Khi nào dùng recursive_mutex?

  • Thường không khuyến khích — code complexity
  • Dùng khi refactor legacy code
  • Dùng trong recursive algorithms

std::shared_mutex (C++17) — Read-Write Lock

Cho phép multiple readers OR single writer:

cpp
#include <shared_mutex>
#include <mutex>
#include <iostream>

class ThreadSafeCache {
    mutable std::shared_mutex mutex_;
    std::unordered_map<int, std::string> cache_;
    
public:
    // Multiple readers allowed
    std::string read(int key) const {
        std::shared_lock<std::shared_mutex> lock(mutex_);
        auto it = cache_.find(key);
        return it != cache_.end() ? it->second : "";
    }
    
    // Exclusive write
    void write(int key, const std::string& value) {
        std::unique_lock<std::shared_mutex> lock(mutex_);
        cache_[key] = value;
    }
};
┌─────────────────────────────────────────────────────────────────┐
│                   SHARED MUTEX BEHAVIOR                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  shared_lock (read):     ✅ ✅ ✅  Multiple readers OK           │
│                                                                 │
│  unique_lock (write):    ✅ ❌ ❌  Only 1 writer, blocks all     │
│                                                                 │
│  Read + Write:           ❌        Writers wait for readers,    │
│                                    readers wait for writers     │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Best Practices

1. Minimize Lock Duration

cpp
// ❌ BAD: Lock quá lâu
void bad() {
    std::lock_guard<std::mutex> lock(mtx);
    prepareData();      // Không cần lock
    processData();      // Cần lock
    logResults();       // Không cần lock
}

// ✅ GOOD: Lock ngắn nhất có thể
void good() {
    prepareData();
    
    {
        std::lock_guard<std::mutex> lock(mtx);
        processData();
    }  // Unlock ngay
    
    logResults();
}

2. Avoid Calling Unknown Code While Holding Lock

cpp
// ❌ DANGEROUS
void risky() {
    std::lock_guard<std::mutex> lock(mtx);
    callback();  // callback có thể cũng lock mtx → DEADLOCK!
}

// ✅ SAFE
void safe() {
    std::vector<Something> localCopy;
    {
        std::lock_guard<std::mutex> lock(mtx);
        localCopy = sharedData;
    }
    
    for (auto& item : localCopy) {
        callback(item);  // Safe, không hold lock
    }
}

3. Use Appropriate Lock Type

SituationLock Type
Simple scope-basedlock_guard
Need unlock/relockunique_lock
Multiple mutexesscoped_lock (C++17)
Read-heavyshared_lock (C++17)
Condition variablesunique_lock

📚 Tổng kết

ClassWhen to Use
std::mutexBasic mutual exclusion
std::lock_guardSimple RAII locking
std::unique_lockFlexible (deferred, manual, timed)
std::scoped_lockMultiple mutexes (C++17)
std::shared_mutexRead-write locks (C++17)
std::recursive_mutexSame thread locks multiple times

➡️ Tiếp theo

Bạn đã biết cách dùng mutex để tránh race conditions. Nhưng có một cái bẫy nguy hiểm hơn đang chờ...

Deadlocks → — Bài toán các Triết gia ăn tối.