一、读者-写者问题
核心:允许多个读者并发进入,写者独占。经典"读者优先"解法用一个 read_count + 两把锁实现。
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <vector>
#include <chrono>
class ReadWriteLock {
std::mutex mtx_;
std::condition_variable cv_;
int readers_ = 0; // 当前正在读的读者数
bool writer_active_ = false; // 是否有写者在写
public:
void read_lock() {
std::unique_lock<std::mutex> lk(mtx_);
cv_.wait(lk, [this]{ return !writer_active_; }); // 无写者才能进
++readers_;
}
void read_unlock() {
std::unique_lock<std::mutex> lk(mtx_);
if (--readers_ == 0) cv_.notify_all(); // 最后一个读者离开,唤醒写者
}
void write_lock() {
std::unique_lock<std::mutex> lk(mtx_);
cv_.wait(lk, [this]{ return !writer_active_ && readers_ == 0; });
writer_active_ = true;
}
void write_unlock() {
std::unique_lock<std::mutex> lk(mtx_);
writer_active_ = false;
cv_.notify_all();
}
};
ReadWriteLock rw;
int shared_data = 0;
void reader(int id) {
for (int i = 0; i < 3; ++i) {
rw.read_lock();
std::cout << "Reader " << id << " reads: " << shared_data << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(50));
rw.read_unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
void writer(int id) {
for (int i = 0; i < 2; ++i) {
rw.write_lock();
++shared_data;
std::cout << "Writer " << id << " writes: " << shared_data << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
rw.write_unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
}
int main() {
std::vector<std::thread> ts;
for (int i = 1; i <= 3; ++i) ts.emplace_back(reader, i);
for (int i = 1; i <= 2; ++i) ts.emplace_back(writer, i);
for (auto& t : ts) t.join();
return 0;
}
二、睡眠理发师问题
理发店有一个理发师、N 把等候椅。无顾客时理发师睡觉;顾客来时若椅子满则离开,否则唤醒理发师
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <chrono>
constexpr int MAX_CHAIRS = 5; // 等候椅数量
class BarberShop {
std::mutex mtx_;
std::condition_variable cv_customer_, cv_barber_;
std::queue<int> waiting_; // 等候的顾客 id
bool barber_sleeping_ = true;
bool customer_ready_ = false;
bool haircut_done_ = false;
bool shop_open_ = true;
public:
// 顾客线程
bool customer(int id) {
std::unique_lock<std::mutex> lk(mtx_);
if (waiting_.size() >= MAX_CHAIRS) {
std::cout << "Customer " << id << " leaves (no chair)\n";
return false;
}
waiting_.push(id);
if (barber_sleeping_) {
barber_sleeping_ = false;
cv_barber_.notify_one(); // 叫醒理发师
}
// 等待轮到自己理发
cv_customer_.wait(lk, [&]{ return !shop_open_ || waiting_.front() == id; });
if (!shop_open_) return false;
waiting_.pop();
customer_ready_ = true;
cv_barber_.notify_one();
// 等待理发完成
cv_customer_.wait(lk, [&]{ return haircut_done_; });
haircut_done_ = false;
std::cout << "Customer " << id << " done\n";
return true;
}
// 理发师线程
void barber() {
while (true) {
std::unique_lock<std::mutex> lk(mtx_);
cv_barber_.wait(lk, [&]{ return !shop_open_ || customer_ready_ || !waiting_.empty(); });
if (!shop_open_ && waiting_.empty() && !customer_ready_) return;
if (!customer_ready_ && !waiting_.empty()) {
cv_customer_.notify_all(); // 让队首顾客进入
}
cv_barber_.wait(lk, [&]{ return customer_ready_; });
lk.unlock();
std::cout << "Barber is cutting hair...\n";
std::this_thread::sleep_for(std::chrono::milliseconds(200));
lk.lock();
customer_ready_ = false;
haircut_done_ = true;
cv_customer_.notify_all();
if (waiting_.empty()) barber_sleeping_ = true;
}
}
void close() {
std::unique_lock<std::mutex> lk(mtx_);
shop_open_ = false;
cv_barber_.notify_all();
cv_customer_.notify_all();
}
};
BarberShop shop;
void barber_thread() { shop.barber(); }
void customer_thread(int id) { shop.customer(id); }
int main() {
std::thread b(barber_thread);
std::vector<std::thread> cs;
for (int i = 1; i <= 8; ++i) {
cs.emplace_back(customer_thread, i);
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
for (auto& t : cs) t.join();
shop.close();
b.join();
return 0;
}
三、吸烟者问题
代理每次随机放两种原料在桌上,拥有第三种原料的吸烟者才能卷烟并抽完,再通知代理继续
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>
#include <cstdlib>
// 三种原料:0=烟草, 1=纸, 2=火柴
// 吸烟者 i 拥有原料 i,缺少其他两种
class Smokers {
std::mutex mtx_;
std::condition_variable cv_smoker_[3], cv_agent_;
bool on_table_[3] = {false, false, false};
bool agent_waiting_ = true;
public:
void agent() {
for (int round = 0; round < 5; ++round) {
std::unique_lock<std::mutex> lk(mtx_);
int a = std::rand() % 3;
int b = (a + 1 + std::rand() % 2) % 3; // 取不同于 a 的另一种
on_table_[a] = on_table_[b] = true;
std::cout << "Agent puts ingredients " << a << " & " << b << "\n";
cv_smoker_[a].notify_all();
cv_smoker_[b].notify_all();
cv_agent_.wait(lk, [&]{ return agent_waiting_; });
agent_waiting_ = false;
lk.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
void smoker(int id) {
while (true) {
std::unique_lock<std::mutex> lk(mtx_);
cv_smoker_[id].wait(lk, [&]{ return on_table_[(id+1)%3] && on_table_[(id+2)%3]; });
on_table_[0] = on_table_[1] = on_table_[2] = false;
std::cout << "Smoker " << id << " rolls & smokes\n";
lk.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(50));
lk.lock();
agent_waiting_ = true;
cv_agent_.notify_one();
}
}
};
int main() {
std::srand(std::time(nullptr));
Smokers s;
std::thread agt(&Smokers::agent, &s);
std::vector<std::thread> sms;
for (int i = 0; i < 3; ++i) sms.emplace_back(&Smokers::smoker, &s, i);
agt.join();
// 演示用,实际中吸烟者线程会一直循环
return 0;
}
四、屏障同步(Barrier)
用于"多线程各自完成一段工作后,等所有线程都到达某点再继续"。C++20 才有 std::barrier,C++11 需要自己实现:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <vector>
class Barrier {
std::mutex mtx_;
std::condition_variable cv_;
int count_, total_;
int generation_ = 0;
public:
explicit Barrier(int n) : count_(n), total_(n) {}
void wait() {
std::unique_lock<std::mutex> lk(mtx_);
int gen = generation_;
if (--count_ == 0) {
count_ = total_;
++generation_;
cv_.notify_all();
} else {
cv_.wait(lk, [&]{ return generation_ != gen; });
}
}
};
Barrier barrier(4);
void worker(int id) {
for (int round = 0; round < 3; ++round) {
std::cout << "Worker " << id << " phase " << round << " done\n";
barrier.wait(); // 所有线程都到齐才进入下一阶段
}
}
int main() {
std::vector<std::thread> ts;
for (int i = 0; i < 4; ++i) ts.emplace_back(worker, i);
for (auto& t : ts) t.join();
return 0;
}
小结
| 问题 | 核心难点 | 常用原语 |
|---|---|---|
| 读者-写者 | 读写互斥、读读并发、避免写者饥饿 | mutex + condition_variable + 计数器 |
| 睡眠理发师 | 有限等待队列、唤醒机制 | mutex + 两个 condition_variable + 队列 |
| 吸烟者 | 资源组合匹配、代理与吸烟者的握手 | mutex + 多个 condition_variable |
| 屏障 | 等待全员到齐再继续 | mutex + condition_variable + 代际计数 |
其中读者-写者和睡眠理发师是 Tanenbaum 书中明确讲到的两大经典 IPC 问题83.136.203,吸烟者和屏障属于常见补充。这些模型也对应后续章节讨论的"进程通信"与"死锁"章节——哲学家就餐用于演示死锁避免,睡眠理发师用于演示资源分配同步,读者-写者用于演示并发控制策略,建议对照原书一起看效果更好。
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/weixin_45144862/article/details/166950351



