1. Vấn đề nó giải quyết
Consumer nên sleep khi queue rỗng thay vì spin, nhưng wakeup không được làm mất state change hay race với shutdown. Condition variable điều phối wait quanh predicate được mutex bảo vệ.
2. Kiến thức cần có
Compiler hỗ trợ C++17, được gọi với warning đầy đủ, cùng các bài trước theo thứ tự của khóa học.
Biết mutex ownership, RAII lock, queue, thread, predicate và spurious wakeup.
3. Ý tưởng cốt lõi
Mutex bảo vệ cả queue lẫn completion flag. Consumer wait với predicate tương đương !queue.empty() || done; wait atomically release mutex khi ngủ rồi reacquire trước khi return.
4. Cú pháp tối thiểu
condition.wait(lock, [&] { return !queue.empty() || done; });5. Cách nó hoạt động
Producer push ba job cố định khi giữ lock và notify sau mỗi state change.
Consumer loop theo predicate, lấy work dưới mutex và chỉ exit khi queue rỗng đồng thời completion true.
Chương trình in
processed sum: 60, tạo test oracle nhỏ để so sánh với dự đoán trước khi biên dịch.
6. Lỗi thường gặp
Wait không predicate dễ sai do spurious wakeup hoặc missed condition, còn đọc completion flag ngoài synchronization tạo race.
Build thành công chưa đủ; phải kiểm tra lifetime, ordering và error path.
7. Khi nào nên dùng
Nên dùng kỹ thuật này khi thread trao đổi work qua shared state và blocking phù hợp hơn spinning.
Nên chọn cách C++11/14 đơn giản hơn nếu quy tắc C++17 không cải thiện safety, độ rõ hoặc hiệu năng đã đo trên các toolchain phải hỗ trợ.
8. Ví dụ đơn giản
Một producer và một consumer dùng một queue cùng shutdown flag rõ. Chỉ final sum xác định được in.
Mã mẫu hoàn chỉnh
Tệp mã nguồn
cpp17/43_mutex_condition_variable_producer_consumer/main.cpp
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <queue>
#include <thread>
int main() {
std::mutex mutex;
std::condition_variable condition;
std::queue<int> jobs;
bool done = false;
int total = 0;
std::thread consumer([&] {
for (;;) {
std::unique_lock lock{mutex};
condition.wait(lock, [&] { return !jobs.empty() || done; });
if (jobs.empty() && done) break;
const int job = jobs.front();
jobs.pop();
lock.unlock();
total += job;
}
});
std::thread producer([&] {
for (int job : {10, 20, 30}) {
{
std::lock_guard lock{mutex};
jobs.push(job);
}
condition.notify_one();
}
{
std::lock_guard lock{mutex};
done = true;
}
condition.notify_one();
});
producer.join();
consumer.join();
std::cout << "processed sum: " << total << '\n';
}
9. Điều cần nhớ
Condition variable wait cho predicate chứ không phải notification; predicate và mọi field nó đọc cần cùng synchronization discipline.
Phải chọn rõ chế độ C++17; default của compiler mới hơn có thể che lỗi portability.
Warning và ví dụ xác định biến quy tắc ghi nhớ thành bằng chứng.
10. Câu hỏi tự kiểm tra
Dễ — Mutex, condition variable và producer–consumer design giải quyết vấn đề chính nào?
Trung bình — Vì sao consumer phải test cả queue state lẫn completion?
Khó — Vì sao đổi predicate state trước notification là ordering pattern quan trọng?