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.
🧠 Quiz
Câu 1: std::lock_guard và std::unique_lock khác nhau chủ yếu ở điểm nào?
- [ ] A)
lock_guardnhanh hơnunique_lockđáng kể - [x] B)
unique_lockcó thể unlock/lock lại thủ công,lock_guardthì không - [ ] C)
lock_guardhỗ trợ nhiều mutex,unique_lockchỉ một - [ ] D) Không có sự khác biệt, chỉ là syntax khác nhau
💡 Giải thích:
std::lock_guardlà RAII wrapper đơn giản — lock khi tạo, unlock khi hủy, không thể thay đổi.std::unique_locklinh hoạt hơn: hỗ trợ deferred locking, try_lock, timed lock, và có thể unlock/lock lại. Dùnglock_guardkhi chỉ cần simple scoped locking,unique_lockkhi cần flexibility.
Câu 2: std::scoped_lock giải quyết vấn đề gì so với nhiều lock_guard?
- [ ] A) Tốc độ lock nhanh hơn
- [x] B) Tránh deadlock bằng cách lock nhiều mutex cùng lúc theo thứ tự nhất quán
- [ ] C) Sử dụng ít bộ nhớ hơn
- [ ] D) Hỗ trợ recursive locking
💡 Giải thích: Khi cần lock nhiều mutex, việc lock theo thứ tự khác nhau ở các thread gây deadlock.
std::scoped_lock(C++17) lock tất cả mutex cùng lúc bằng thuật toán tránh deadlock, tương tựstd::lock()nhưng kết hợp RAII. Đây là best practice khi cần lock 2+ mutex.
Câu 3: Race condition xảy ra khi nào?
- [ ] A) Khi hai thread cùng đọc một biến shared
- [x] B) Khi ít nhất một thread ghi vào shared data mà không có synchronization
- [ ] C) Khi dùng quá nhiều mutex trong chương trình
- [ ] D) Khi thread bị block quá lâu bởi mutex
💡 Giải thích: Race condition xảy ra khi hai hoặc nhiều threads truy cập cùng shared data đồng thời, và ít nhất một thread thực hiện ghi (write), mà không có cơ chế đồng bộ hóa. Nhiều threads cùng đọc (read-only) thì hoàn toàn an toàn. Đây là lý do
std::shared_mutexcho phép nhiều readers nhưng chỉ một writer.