Giao diện
🔒 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
| Feature | lock_guard | unique_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 lockedDeferred 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
| Situation | Lock Type |
|---|---|
| Simple scope-based | lock_guard |
| Need unlock/relock | unique_lock |
| Multiple mutexes | scoped_lock (C++17) |
| Read-heavy | shared_lock (C++17) |
| Condition variables | unique_lock |
📚 Tổng kết
| Class | When to Use |
|---|---|
std::mutex | Basic mutual exclusion |
std::lock_guard | Simple RAII locking |
std::unique_lock | Flexible (deferred, manual, timed) |
std::scoped_lock | Multiple mutexes (C++17) |
std::shared_mutex | Read-write locks (C++17) |
std::recursive_mutex | Same 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.