【操作系统】进程同步与互斥实验报告
目录
三、临界区基础软件实现算法的完整实验代码(Linux系统编译运行)
五、基于原子指令实现自旋锁的完整实验代码(Linux系统编译运行)
七、信号量(Semaphore)同步机制的完整实验代码(Linux系统编译运行)
九、管程(Monitor)的完整实验代码(Linux系统编译运行)
一、实验概述
进程并发执行时,会存在两类核心问题:互斥与同步。
- 互斥:多个进程竞争访问临界资源(打印机、共享变量、缓冲区),同一时刻仅允许一个进程进入临界区;
- 同步:进程间存在执行先后顺序约束,生产者完成生产后消费者才能消费,形成有序协作。
若缺少同步互斥机制,会引发竞争条件(Race Condition)、数据不一致、死锁、饥饿等问题。本次实验依次实现:临界区基础算法、原子自旋锁、信号量机制、管程(Monitor)四大层级同步方案,对比不同方案优缺点,验证经典同步问题(生产者 - 消费者、读者写者、哲学家就餐、睡眠理发师、同步屏障)。
二、临界区基础软件实现算法(纯共享变量,无内核原语)
临界区问题四条准则:空闲让进、忙则等待、有限等待、让权等待。
1. 单标志法
思想:设置turn变量,仅允许编号等于turn的线程进入临界区。
while (turn != tid) {}
// 临界区
turn = other;
缺陷:强制交替执行,不满足空闲让进。线程 0 执行完毕后,即便线程 1 阻塞,线程 0 也无法再次进入临界区;仅适合两个线程严格轮流场景。
2. 双标志先检查法
先检测对方标志,再置位自身标志。
while(flag[other]){}
flag[me]=true;
缺陷:存在漏洞窗口。两个线程同时检测发现对方flag=false,随后同时设置自身标志,同时进入临界区,发生冲突,无法保证互斥。
3. 双标志后检查法
先设置自身标志,再检测对方标志。
flag[me]=true;
while(flag[other]){}
缺陷:极易死锁。两线程同时把标志置true,随后互相等待,永久阻塞。
4. Peterson 算法(双线程标准临界区算法)
融合标志 + 轮转序号,同时满足四条准则。
flag[me]=true;
turn=other;
while(flag[other] && turn==other){}
//临界区
flag[me]=false;
flag:表达进入临界区意愿;turn:发生冲突时,礼让对方优先进入;
同时满足:空闲让进、忙则等待、有限等待。 局限:仅支持两个线程,无法直接拓展多线程场景。
结论:纯软件临界区算法存在明显局限,工业环境依靠硬件原子指令实现锁。
三、临界区基础软件实现算法的完整实验代码(Linux系统编译运行)
mutex_algo.cpp
#include <iostream>
#include <thread>
#include <atomic>
#include <chrono>
#include <random>
const int LOOP_CNT = 10;
// 简易随机短暂忙等,制造线程竞争窗口
static void tiny_delay()
{
static std::mt19937 rng(std::random_device{}());
std::uniform_int_distribution<int> dist(0, 800);
int loop = dist(rng);
for(int k = 0; k < loop; k++){}
}
//=============================================
// 1. 单标志法
//=============================================
void runSingleFlag()
{
std::atomic<int> turn{0};
std::atomic<int> critical_inside{0};
std::atomic<int> conflict_cnt{0};
auto t0_func = [&]()
{
for (int i = 0; i < LOOP_CNT; ++i)
{
while (turn != 0) {}
critical_inside++;
if (critical_inside > 1)
{
conflict_cnt++;
std::cout << "[单标志] !!!冲突发生!!!\n";
}
std::cout << "[SingleFlag Thread0] i = " << i << std::endl;
critical_inside--;
turn = 1;
}
};
auto t1_func = [&]()
{
for (int i = 0; i < LOOP_CNT; ++i)
{
while (turn != 1) {}
critical_inside++;
if (critical_inside > 1)
{
conflict_cnt++;
std::cout << "[单标志] !!!冲突发生!!!\n";
}
std::cout << "[SingleFlag Thread1] i = " << i << std::endl;
critical_inside--;
turn = 0;
}
};
std::thread t0(t0_func);
std::thread t1(t1_func);
t0.join();
t1.join();
std::cout << "\n=====【单标志法】执行结束,冲突次数:" << conflict_cnt << " =====\n\n";
}
//=============================================
// 2. 双标志先检查法(加入tiny_delay制造竞争)
//=============================================
void runDoubleFlagFirst()
{
std::atomic<bool> flag[2] = {false, false};
std::atomic<int> critical_inside{0};
std::atomic<int> conflict_cnt{0};
auto t0_func = [&]()
{
int other = 1;
for (int i = 0; i < LOOP_CNT; ++i)
{
while (flag[other]) {}
tiny_delay(); // 扩大漏洞窗口!
flag[0] = true;
critical_inside++;
if (critical_inside > 1)
{
conflict_cnt++;
std::cout << "[双标志先检查] !!!冲突发生!!!\n";
}
std::cout << "[DoubleFirst Thread0] i = " << i << std::endl;
critical_inside--;
flag[0] = false;
}
};
auto t1_func = [&]()
{
int other = 0;
for (int i = 0; i < LOOP_CNT; ++i)
{
while (flag[other]) {}
tiny_delay(); // 扩大漏洞窗口!
flag[1] = true;
critical_inside++;
if (critical_inside > 1)
{
conflict_cnt++;
std::cout << "[双标志先检查] !!!冲突发生!!!\n";
}
std::cout << "[DoubleFirst Thread1] i = " << i << std::endl;
critical_inside--;
flag[1] = false;
}
};
std::thread t0(t0_func);
std::thread t1(t1_func);
t0.join();
t1.join();
std::cout << "\n=====【双标志先检查法】执行结束,冲突次数:" << conflict_cnt << " =====\n\n";
}
//=============================================
// 3. 双标志后检查法(更容易触发死锁)
//=============================================
void runDoubleFlagLast()
{
std::atomic<bool> flag[2] = {false, false};
std::atomic<int> critical_inside{0};
std::atomic<int> conflict_cnt{0};
auto t0_func = [&]()
{
int other = 1;
for (int i = 0; i < LOOP_CNT; ++i)
{
flag[0] = true;
tiny_delay();
while (flag[other]) {}
critical_inside++;
if (critical_inside > 1)
{
conflict_cnt++;
std::cout << "[双标志后检查] !!!冲突发生!!!\n";
}
std::cout << "[DoubleLast Thread0] i = " << i << std::endl;
critical_inside--;
flag[0] = false;
}
};
auto t1_func = [&]()
{
int other = 0;
for (int i = 0; i < LOOP_CNT; ++i)
{
flag[1] = true;
tiny_delay();
while (flag[other]) {}
critical_inside++;
if (critical_inside > 1)
{
conflict_cnt++;
std::cout << "[双标志后检查] !!!冲突发生!!!\n";
}
std::cout << "[DoubleLast Thread1] i = " << i << std::endl;
critical_inside--;
flag[1] = false;
}
};
std::thread t0(t0_func);
std::thread t1(t1_func);
t0.join();
t1.join();
std::cout << "\n=====【双标志后检查法】执行结束,冲突次数:" << conflict_cnt << " =====\n\n";
}
//=============================================
// 4. Peterson算法
//=============================================
void runPeterson()
{
std::atomic<bool> flag[2]{false, false};
std::atomic<int> turn{0};
std::atomic<int> critical_inside{0};
std::atomic<int> conflict_cnt{0};
auto t0_func = [&]()
{
int other = 1;
for (int i = 0; i < LOOP_CNT; ++i)
{
flag[0] = true;
turn = other;
tiny_delay();
while (flag[other] && turn == other) {}
critical_inside++;
if (critical_inside > 1)
{
conflict_cnt++;
std::cout << "[Peterson] !!!冲突发生!!!\n";
}
std::cout << "[Peterson Thread0] i = " << i << std::endl;
critical_inside--;
flag[0] = false;
}
};
auto t1_func = [&]()
{
int other = 0;
for (int i = 0; i < LOOP_CNT; ++i)
{
flag[1] = true;
turn = other;
tiny_delay();
while (flag[other] && turn == other) {}
critical_inside++;
if (critical_inside > 1)
{
conflict_cnt++;
std::cout << "[Peterson] !!!冲突发生!!!\n";
}
std::cout << "[Peterson Thread1] i = " << i << std::endl;
critical_inside--;
flag[1] = false;
}
};
std::thread t0(t0_func);
std::thread t1(t1_func);
t0.join();
t1.join();
std::cout << "\n=====【Peterson算法】执行结束,冲突次数:" << conflict_cnt << " =====\n\n";
}
int main()
{
std::cout << "========== 开始执行【单标志法】 ==========\n";
runSingleFlag();
std::cout << "========== 开始执行【双标志先检查法】 ==========\n";
runDoubleFlagFirst();
std::cout << "========== 开始执行【双标志后检查法】 ==========\n";
runDoubleFlagLast();
std::cout << "========== 开始执行【Peterson算法】 ==========\n";
runPeterson();
std::cout << "\n全部算法执行完毕\n";
return 0;
}
Linux 编译运行命令
g++ mutex_algo.cpp -o mutex_algo -pthread -O0
./mutex_algo
程序运行结果展示
========== 开始执行【单标志法】 ==========
[SingleFlag Thread0] i = 0
[SingleFlag Thread1] i = 0
[SingleFlag Thread0] i = 1
[SingleFlag Thread1] i = 1
[SingleFlag Thread0] i = 2
[SingleFlag Thread1] i = 2
[SingleFlag Thread0] i = 3
[SingleFlag Thread1] i = 3
[SingleFlag Thread0] i = 4
[SingleFlag Thread1] i = 4
[SingleFlag Thread0] i = 5
[SingleFlag Thread1] i = 5
[SingleFlag Thread0] i = 6
[SingleFlag Thread1] i = 6
[SingleFlag Thread0] i = 7
[SingleFlag Thread1] i = 7
[SingleFlag Thread0] i = 8
[SingleFlag Thread1] i = 8
[SingleFlag Thread0] i = 9
[SingleFlag Thread1] i = 9
=====【单标志法】执行结束,冲突次数:0 =====
========== 开始执行【双标志先检查法】 ==========
[DoubleFirst Thread0] i = 0
[DoubleFirst Thread0] i = 1
[DoubleFirst Thread0] i = 2
[DoubleFirst Thread0] i = 3
[DoubleFirst Thread0] i = 4
[DoubleFirst Thread0] i = 5
[DoubleFirst Thread0] i = 6
[DoubleFirst Thread0] i = 7
[DoubleFirst Thread0] i = 8
[DoubleFirst Thread0] i = 9
[DoubleFirst Thread1] i = 0
[DoubleFirst Thread1] i = 1
[DoubleFirst Thread1] i = 2
[DoubleFirst Thread1] i = 3
[DoubleFirst Thread1] i = 4
[DoubleFirst Thread1] i = 5
[DoubleFirst Thread1] i = 6
[DoubleFirst Thread1] i = 7
[DoubleFirst Thread1] i = 8
[DoubleFirst Thread1] i = 9
=====【双标志先检查法】执行结束,冲突次数:0 =====
========== 开始执行【双标志后检查法】 ==========
[DoubleLast Thread0] i = 0
[DoubleLast Thread0] i = 1
[DoubleLast Thread0] i = 2
[DoubleLast Thread0] i = 3
[DoubleLast Thread0] i = 4
[DoubleLast Thread0] i = 5
[DoubleLast Thread0] i = 6
[DoubleLast Thread0] i = 7
[DoubleLast Thread0] i = 8
[DoubleLast Thread0] i = 9
[DoubleLast Thread1] i = 0
[DoubleLast Thread1] i = 1
[DoubleLast Thread1] i = 2
[DoubleLast Thread1] i = 3
[DoubleLast Thread1] i = 4
[DoubleLast Thread1] i = 5
[DoubleLast Thread1] i = 6
[DoubleLast Thread1] i = 7
[DoubleLast Thread1] i = 8
[DoubleLast Thread1] i = 9
=====【双标志后检查法】执行结束,冲突次数:0 =====
========== 开始执行【Peterson算法】 ==========
[Peterson Thread0] i = 0
[Peterson Thread0] i = 1
[Peterson Thread0] i = 2
[Peterson Thread0] i = 3
[Peterson Thread0] i = 4
[Peterson Thread0] i = 5
[Peterson Thread0] i = 6
[Peterson Thread0] i = 7
[Peterson Thread0] i = 8
[Peterson Thread0] i = 9
[Peterson Thread1] i = 0
[Peterson Thread1] i = 1
[Peterson Thread1] i = 2
[Peterson Thread1] i = 3
[Peterson Thread1] i = 4
[Peterson Thread1] i = 5
[Peterson Thread1] i = 6
[Peterson Thread1] i = 7
[Peterson Thread1] i = 8
[Peterson Thread1] i = 9
=====【Peterson算法】执行结束,冲突次数:0 =====
全部算法执行完毕
四、基于原子指令实现自旋锁(用户态忙等锁)
现代 CPU 提供原子读写指令,实现自旋锁,线程无法进入临界区时循环忙等。
1. TAS(Test-and-Set)测试并置位
while(flag.exchange(true));
不断原子交换:尝试将锁置true,返回旧值。锁空闲则获取成功;被占用则持续循环。
2. CAS(Compare-and-Swap)比较交换
while(!cas(flag, false, true));
预期值匹配才修改,是无锁编程基石。
自旋锁特点
无需陷入内核,获取释放速度快;
失败线程持续 CPU 忙等,浪费算力;适合临界区极短场景;长时间临界区应当使用互斥锁(阻塞休眠)。
五、基于原子指令实现自旋锁的完整实验代码(Linux系统编译运行)
atomic_demo.cpp
#include <iostream>
#include <thread>
#include <atomic>
const int LOOP_NUM = 100000;
// ==============================
// 1. 模拟关中断自旋锁
// ==============================
struct FakeCliLock
{
void lock()
{
// 用户态无法执行cli特权指令
}
void unlock()
{
}
};
// ==============================
// 2. TAS Test-and-Set
// ==============================
struct TASLock
{
std::atomic<bool> flag{false};
void lock()
{
while (flag.exchange(true, std::memory_order_acquire))
{
}
}
void unlock()
{
flag.store(false, std::memory_order_release);
}
};
// ==============================
// 3. Swap 原子交换自旋锁
// ==============================
struct SwapLock
{
std::atomic<bool> flag{false};
void lock()
{
bool expected;
do
{
expected = false;
} while (!flag.compare_exchange_weak(expected, true, std::memory_order_acquire));
}
void unlock()
{
flag.store(false, std::memory_order_release);
}
};
// ==============================
// 4. CAS Compare-and-Swap
// ==============================
struct CASLock
{
std::atomic<bool> flag{false};
void lock()
{
bool expect = false;
while (!flag.compare_exchange_weak(expect, true,
std::memory_order_acquire,
std::memory_order_relaxed))
{
expect = false;
}
}
void unlock()
{
flag.store(false, std::memory_order_release);
}
};
// 通用测试函数:传入锁对象,执行并发累加
template<typename LockType>
void runTest(const char* name)
{
LockType lock_obj;
std::atomic<long long> cnt{0};
auto worker = [&]()
{
for(int i = 0; i < LOOP_NUM; i++)
{
lock_obj.lock();
cnt++;
lock_obj.unlock();
}
};
std::thread t1(worker);
std::thread t2(worker);
t1.join();
t2.join();
long long expect = (long long)LOOP_NUM * 2;
std::cout << "========== " << name << " ==========\n";
std::cout << "预期值: " << expect << "\n";
std::cout << "实际值: " << cnt.load() << "\n\n";
}
int main()
{
runTest<FakeCliLock>("【模拟关中断 FakeCliLock】");
runTest<TASLock>("【TAS 测试并置位】");
runTest<SwapLock>("【Swap原子交换锁】");
runTest<CASLock>("【CAS比较交换锁】");
std::cout << "全部测试执行完毕\n";
return 0;
}
Linux 编译运行命令
g++ atomic_demo.cpp -o atomic_demo -pthread -O0
./atomic_demo
程序运行结果展示
========== 【模拟关中断 FakeCliLock】 ==========
预期值: 200000
实际值: 200000
========== 【TAS 测试并置位】 ==========
预期值: 200000
实际值: 200000
========== 【Swap原子交换锁】 ==========
预期值: 200000
实际值: 200000
========== 【CAS比较交换锁】 ==========
预期值: 200000
实际值: 200000
全部测试执行完毕
六、信号量(Semaphore)同步机制
Dijkstra 提出,通过P()申请、V()释放资源,统一解决互斥与同步。
//P操作:count--;资源不足则阻塞等待
void P();
//V操作:count++;唤醒一个等待线程
void V();
- 二元信号量(count=1):用作互斥锁;
- 计数信号量(count=N):限制最大并发数量,用于缓冲区空位、读者数量控制。
经典同步问题(信号量实现)
1. 生产者 - 消费者(有界缓冲区)
变量:空槽empty、产品full、缓冲区互斥锁。 规则: 生产者先申请空位,放入产品,唤醒消费者; 消费者先申请产品,取出产品,释放空位。
重要规范:同步信号量 P 在前,互斥锁 P 在后;禁止颠倒,防止死锁。
2. 读者优先读者 - 写者
- 多个读者可同时读;写者必须独占;
- 第一个读者加写锁,最后一个读者释放写锁;
缺陷:持续到来的读者会导致写者饥饿。
3. 写者优先读者 - 写者
新增写等待计数器。当存在等待写者时,新读者禁止进入;保证写请求优先处理,消除写饥饿。
4. 哲学家就餐(5 人 5 叉子)
原始方案:每位哲学家同时拿左右叉子,形成环路等待→死锁。 两种改良方案:
- 限制最多 4 人进入餐厅,破坏环路等待;
- 奇偶编号策略:偶数先拿左叉,奇数先拿右叉。
5. 睡眠理发师问题
理发店:理发师无顾客则睡眠;顾客到来唤醒理发师;等待座位满则直接离开。 模型要素:等待顾客计数、顾客信号量、关门退出标记;模拟服务系统生产者消费者变体。
6. 同步屏障 Barrier
所有线程执行至屏障点,全部抵达后才能统一继续往下执行。常用于并行计算多阶段同步。
信号量缺点
P/V分散在程序各处,极易配对错误(漏 V、重复 V、顺序颠倒),引发死锁或资源错乱。为解决该问题,诞生管程机制。
七、信号量(Semaphore)同步机制的完整实验代码(Linux系统编译运行)
sync_demo.cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <vector>
#include <chrono>
#include <memory>
#include <atomic>
//=========================
// 自制信号量(二元信号量 / 计数信号量)
//=========================
class Semaphore {
private:
std::mutex mtx;
std::condition_variable cv;
int count;
public:
explicit Semaphore(int init_val) : count(init_val) {}
// 禁止拷贝 & 移动
Semaphore(const Semaphore&) = delete;
Semaphore& operator=(const Semaphore&) = delete;
Semaphore(Semaphore&&) = delete;
Semaphore& operator=(Semaphore&&) = delete;
// P操作:申请资源
void P() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [this](){ return count > 0; });
count--;
}
// V操作:释放资源
void V() {
std::lock_guard<std::mutex> lock(mtx);
count++;
cv.notify_one();
}
};
//=========================
// 1. 生产者-消费者模型(计数信号量实现)
//=========================
void testProducerConsumer()
{
std::cout << "\n==========【生产者-消费者 计数信号量】==========\n";
const int BUF_SIZE = 5;
const int TOTAL_PRODUCE = 8;
std::vector<int> buffer;
Semaphore empty(BUF_SIZE);
Semaphore full(0);
std::mutex buf_mtx;
auto producer = [&](){
for(int i = 0; i < TOTAL_PRODUCE; ++i)
{
empty.P();
{
std::lock_guard<std::mutex> lk(buf_mtx);
buffer.push_back(i);
std::cout << "[生产者] 生产:" << i << "\n";
}
full.V();
std::this_thread::sleep_for(std::chrono::milliseconds(80));
}
};
auto consumer = [&](){
for(int i = 0; i < TOTAL_PRODUCE; ++i)
{
full.P();
int item;
{
std::lock_guard<std::mutex> lk(buf_mtx);
item = buffer.front();
buffer.erase(buffer.begin());
}
empty.V();
std::cout << "[消费者] 消费:" << item << "\n";
std::this_thread::sleep_for(std::chrono::milliseconds(120));
}
};
std::thread t_prod(producer);
std::thread t_cons(consumer);
t_prod.join();
t_cons.join();
}
//=========================
// 2. 读者优先 读者-写者问题
//=========================
void testReadFirstRW()
{
std::cout << "\n==========【读者优先 读者-写者】==========\n";
int data = 100;
int read_cnt = 0;
std::mutex cnt_mtx;
Semaphore w_lock(1);
auto reader = [&](int id){
for(int i = 0; i < 2; ++i)
{
{
std::lock_guard<std::mutex> lk(cnt_mtx);
if(read_cnt == 0)
{
w_lock.P();
}
read_cnt++;
}
std::cout << "[读者" << id << "] 读取 data = " << data << "\n";
std::this_thread::sleep_for(std::chrono::milliseconds(60));
{
std::lock_guard<std::mutex> lk(cnt_mtx);
read_cnt--;
if(read_cnt == 0)
{
w_lock.V();
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(40));
}
};
auto writer = [&](){
for(int i = 0; i < 2; ++i)
{
w_lock.P();
data += 10;
std::cout << "[写者] 修改 data = " << data << "\n";
std::this_thread::sleep_for(std::chrono::milliseconds(100));
w_lock.V();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
};
std::thread r1(reader,1);
std::thread r2(reader,2);
std::thread w(writer);
r1.join();
r2.join();
w.join();
}
//=========================
// 3. 写者优先 读者-写者(防止写饥饿,信号量实现)
//=========================
void testWriteFirstRW()
{
std::cout << "\n==========【写者优先 读者-写者】==========\n";
int data = 100;
int read_cnt = 0;
std::mutex mtx;
Semaphore w_sem(1);
Semaphore r_sem(1);
auto reader = [&](int id){
for(int i = 0; i < 2; ++i){
r_sem.P();
{
std::lock_guard<std::mutex> lk(mtx);
if(read_cnt == 0) w_sem.P();
read_cnt++;
}
r_sem.V();
std::cout << "[读者" << id << "] 读 data=" << data << "\n";
std::this_thread::sleep_for(std::chrono::milliseconds(50));
r_sem.P();
{
std::lock_guard<std::mutex> lk(mtx);
read_cnt--;
if(read_cnt == 0) w_sem.V();
}
r_sem.V();
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
};
auto writer = [&](){
for(int i = 0; i < 2; ++i){
w_sem.P();
data += 20;
std::cout << "[写者] 修改 data=" << data << "\n";
std::this_thread::sleep_for(std::chrono::milliseconds(100));
w_sem.V();
std::this_thread::sleep_for(std::chrono::milliseconds(80));
}
};
std::thread r1(reader,1);
std::thread r2(reader,2);
std::thread w(writer);
r1.join();
r2.join();
w.join();
}
//=========================
// 4. 哲学家就餐问题
//=========================
void testDiningPhilosopher()
{
std::cout << "\n==========【哲学家就餐(防死锁)】==========\n";
const int PHIL_NUM = 5;
std::vector<std::unique_ptr<Semaphore>> fork;
for(int i = 0; i < PHIL_NUM; i++)
{
fork.emplace_back(std::unique_ptr<Semaphore>(new Semaphore(1)));
}
Semaphore room(4); // 房间最多4人,破坏环路等待
auto philosopher = [&](int id){
int left = id;
int right = (id + 1) % PHIL_NUM;
for(int i = 0; i < 2; ++i)
{
std::cout << "[哲学家" << id << "] 思考\n";
std::this_thread::sleep_for(std::chrono::milliseconds(70));
room.P();
fork[left]->P();
fork[right]->P();
std::cout << "[哲学家" << id << "] 就餐\n";
std::this_thread::sleep_for(std::chrono::milliseconds(100));
fork[right]->V();
fork[left]->V();
room.V();
}
};
std::vector<std::thread> phs;
for(int i = 0; i < PHIL_NUM; ++i)
{
phs.emplace_back(philosopher, i);
}
for(auto &t : phs) t.join();
}
//=========================
// 5. 睡眠理发师问题
//=========================
void testSleepBarber()
{
std::cout << "\n==========【睡眠理发师问题】==========\n";
const int WAIT_CHAIR = 4;
int customer_num = 0;
std::mutex mtx;
Semaphore customer(0);
std::atomic<bool> barber_exit{false};
auto barber_func = [&](){
while(!barber_exit.load())
{
customer.P();
if(barber_exit.load()) break;
std::lock_guard<std::mutex> lk(mtx);
customer_num--;
std::cout << "[理发师] 开始理发\n";
std::this_thread::sleep_for(std::chrono::milliseconds(120));
std::cout << "[理发师] 理发完成\n";
}
std::cout << "[理发师] 今日营业结束\n";
};
auto customer_func = [&](int id){
std::lock_guard<std::mutex> lk(mtx);
if(customer_num < WAIT_CHAIR)
{
customer_num++;
std::cout << "[顾客" << id << "] 等待理发\n";
customer.V();
}else{
std::cout << "[顾客" << id << "] 座位已满,离开\n";
}
};
std::thread barber_t(barber_func);
std::vector<std::thread> custs;
for(int i = 1; i <= 6; ++i)
{
custs.emplace_back(customer_func, i);
std::this_thread::sleep_for(std::chrono::milliseconds(60));
}
for(auto &t : custs) t.join();
barber_exit.store(true);
customer.V();
barber_t.join();
}
//=========================
// 6. 屏障 Barrier 模拟实现(所有线程到达统一放行)
//=========================
class Barrier
{
private:
std::mutex mtx;
std::condition_variable cv;
int total;
int arrived;
public:
explicit Barrier(int n) : total(n), arrived(0) {}
Barrier(const Barrier&) = delete;
Barrier& operator=(const Barrier&) = delete;
Barrier(Barrier&&) = delete;
Barrier& operator=(Barrier&&) = delete;
void wait()
{
std::unique_lock<std::mutex> lock(mtx);
arrived++;
if(arrived == total)
{
arrived = 0;
cv.notify_all();
}else{
cv.wait(lock);
}
}
};
void testBarrier()
{
std::cout << "\n==========【同步屏障 Barrier】==========\n";
const int THREAD_CNT = 4;
Barrier bar(THREAD_CNT);
auto worker = [&](int id){
for(int round = 1; round <= 2; ++round)
{
std::cout << "[线程" << id << "] 完成阶段" << round << "任务,等待其他线程\n";
bar.wait();
std::cout << "[线程" << id << "] === 全体就绪,进入下一阶段 ===\n";
}
};
std::vector<std::thread> ths;
for(int i = 1; i <= THREAD_CNT; ++i)
ths.emplace_back(worker, i);
for(auto &t : ths) t.join();
}
//=========================
// 主函数:依次运行全部实验
//=========================
int main()
{
std::cout << "============ 高级同步原语综合实验 ============\n";
testProducerConsumer();
testReadFirstRW();
testWriteFirstRW();
testDiningPhilosopher();
testSleepBarber();
testBarrier();
std::cout << "\n============ 所有实验执行完毕 ============\n";
return 0;
}
Linux 编译运行命令
g++ sync_demo.cpp -o sync_demo -std=c++11 -pthread
./sync_demo
程序运行结果展示
============ 高级同步原语综合实验 ============
==========【生产者-消费者 计数信号量】==========
[生产者] 生产:0
[消费者] 消费:0
[生产者] 生产:1
[消费者] 消费:1
[生产者] 生产:2
[消费者] 消费:2
[生产者] 生产:3
[生产者] 生产:4
[消费者] 消费:3
[生产者] 生产:5
[消费者] 消费:4
[生产者] 生产:6
[生产者] 生产:7
[消费者] 消费:5
[消费者] 消费:6
[消费者] 消费:7
==========【读者优先 读者-写者】==========
[读者1] 读取 data = 100
[读者2] 读取 data = 100
[写者] 修改 data = 110
[读者2] 读取 data = 110
[读者1] 读取 data = 110
[写者] 修改 data = 120
==========【写者优先 读者-写者】==========
[读者1] 读 data=100
[读者2] 读 data=100
[写者] 修改 data=120
[读者1] 读 data=120
[读者2] 读 data=120
[写者] 修改 data=140
==========【哲学家就餐(防死锁)】==========
[哲学家0] 思考
[哲学家1] 思考
[哲学家2] 思考
[哲学家3] 思考
[哲学家4] 思考
[哲学家0] 就餐
[哲学家2] 就餐
[哲学家0] 思考
[哲学家2] 思考
[哲学家1] 就餐
[哲学家4] 就餐
[哲学家3] 就餐
[哲学家[哲学家1] 思考
[哲学家0] 就餐
4] 思考
[哲学家3] 思考
[哲学家2] 就餐
[哲学家4] 就餐
[哲学家3] 就餐
[哲学家1] 就餐
==========【睡眠理发师问题】==========
[顾客1] 等待理发
[理发师] 开始理发
[理发师] 理发完成
[顾客2] 等待理发
[理发师] 开始理发
[理发师] 理发完成
[顾客3] 等待理发
[顾客4] 等待理发
[理发师] 开始理发
[理发师] 理发完成
[理发师] 开始理发
[理发师] 理发完成
[顾客6] 等待理发
[顾客5] 等待理发
[理发师] 开始理发
[理发师] 理发完成
[理发师] 今日营业结束
==========【同步屏障 Barrier】==========
[线程1] 完成阶段1任务,等待其他线程
[线程2] 完成阶段1任务,等待其他线程
[线程3] 完成阶段1任务,等待其他线程
[线程4] 完成阶段1任务,等待其他线程
[线程4] === 全体就绪,进入下一阶段 ===
[线程4] 完成阶段2任务,等待其他线程
[线程3] === 全体就绪,进入下一阶段 ===
[线程3] 完成阶段2任务,等待其他线程
[线程1] === 全体就绪,进入下一阶段 ===
[线程1] 完成阶段2任务,等待其他线程
[线程2] === 全体就绪,进入下一阶段 ===
[线程2] 完成阶段2任务,等待其他线程
[线程2] === 全体就绪,进入下一阶段 ===
[线程1] === 全体就绪,进入下一阶段 ===
[线程3] === 全体就绪,进入下一阶段 ===
[线程4] === 全体就绪,进入下一阶段 ===
============ 所有实验执行完毕 ============
八、管程(Monitor)—— 面向对象的高层同步原语
1. 管程核心定义
管程将共享数据、访问数据的过程、互斥与条件等待机制封装为一个整体。
强制语义:同一时刻最多一个线程驻留管程内部,天然实现互斥。
基础接口:
enter():进入管程,上锁;leave():退出管程,解锁;wait():释放管程锁,条件阻塞;唤醒后重新获取锁;signal()唤醒一个等待线程;broadcast()唤醒全部。
区分两种模型
- Hoare 模型(Signal-and-Transfer):发出 signal 的线程让出管程,被唤醒线程立即执行;
- Hansen 模型(Signal-and-Continue):发出 signal 后继续运行;本次实验采用该模型,工程最常用。
2. 管程优势
- 互斥由管程内部自动保障,程序员无需手动管理大量信号量;
- 共享资源与操作封装在一起,代码结构清晰,减少同步错误;
- 条件变量封装在管程内部,不存在信号量跨区域滥用。
3. 管程实现经典同步问题
将生产者缓冲区、读写数据、叉子资源、理发店状态全部封装进管程类。
所有临界操作必须调用管程成员方法,从语法层面约束线程不能绕过同步逻辑访问共享变量。
对比信号量:信号量是 “分散式同步”;管程是 “封装式同步”。Java
synchronized、C#lock底层思想均源自管程。
九、管程(Monitor)的完整实验代码(Linux系统编译运行)
monitor_all.cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <vector>
#include <chrono>
#include <atomic>
#include <memory>
//=========================
// 通用管程 Monitor 基类
//=========================
class Monitor
{
protected:
std::mutex mtx;
std::condition_variable cv;
public:
void enter()
{
mtx.lock();
}
void leave()
{
mtx.unlock();
}
void wait()
{
std::unique_lock<std::mutex> lk(mtx, std::adopt_lock);
cv.wait(lk);
lk.release();
}
void signal()
{
cv.notify_one();
}
void broadcast()
{
cv.notify_all();
}
};
//=========================
// 1. 管程实现:生产者-消费者(有界缓冲区)
//=========================
class PCMonitor : public Monitor
{
private:
std::vector<int> buffer;
const int capacity;
public:
explicit PCMonitor(int cap) : capacity(cap) {}
void insert(int item)
{
enter();
while (buffer.size() >= capacity)
{
wait();
}
buffer.push_back(item);
std::cout << "[生产者] 生产:" << item << "\n";
signal();
leave();
}
int remove()
{
enter();
while (buffer.empty())
{
wait();
}
int item = buffer.front();
buffer.erase(buffer.begin());
std::cout << "[消费者] 消费:" << item << "\n";
signal();
leave();
return item;
}
};
void testProducerConsumer()
{
std::cout << "\n==========【管程|生产者-消费者】==========\n";
const int BUF_SIZE = 5;
const int TOTAL_PRODUCE = 8;
PCMonitor buf(BUF_SIZE);
auto producer = [&](){
for(int i = 0; i < TOTAL_PRODUCE; ++i)
{
buf.insert(i);
std::this_thread::sleep_for(std::chrono::milliseconds(80));
}
};
auto consumer = [&](){
for(int i = 0; i < TOTAL_PRODUCE; ++i)
{
buf.remove();
std::this_thread::sleep_for(std::chrono::milliseconds(120));
}
};
std::thread t_prod(producer);
std::thread t_cons(consumer);
t_prod.join();
t_cons.join();
}
//=========================
// 2. 管程实现:读者优先 读者-写者
//=========================
class ReadFirstRWMonitor : public Monitor
{
private:
int data;
int read_cnt;
bool writing;
public:
ReadFirstRWMonitor() : data(100), read_cnt(0), writing(false) {}
void readData(int id)
{
enter();
while (writing)
{
wait();
}
read_cnt++;
std::cout << "[读者" << id << "] 读取 data = " << data << "\n";
leave();
std::this_thread::sleep_for(std::chrono::milliseconds(60));
enter();
read_cnt--;
if (read_cnt == 0)
{
signal();
}
leave();
}
void writeData()
{
enter();
while (writing || read_cnt > 0)
{
wait();
}
writing = true;
data += 10;
std::cout << "[写者] 修改 data = " << data << "\n";
leave();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
enter();
writing = false;
broadcast();
leave();
}
};
void testReadFirstRW()
{
std::cout << "\n==========【管程|读者优先 读者-写者】==========\n";
ReadFirstRWMonitor rw;
auto reader = [&](int id){
for(int i = 0; i < 2; ++i)
{
rw.readData(id);
std::this_thread::sleep_for(std::chrono::milliseconds(40));
}
};
auto writer = [&](){
for(int i = 0; i < 2; ++i)
{
rw.writeData();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
};
std::thread r1(reader,1);
std::thread r2(reader,2);
std::thread w(writer);
r1.join();
r2.join();
w.join();
}
//=========================
// 3. 管程实现:写者优先 读者-写者(避免写饥饿)
//=========================
class WriteFirstRWMonitor : public Monitor
{
private:
int data;
int read_cnt;
int write_wait;
bool writing;
public:
WriteFirstRWMonitor() : data(100), read_cnt(0), write_wait(0), writing(false) {}
void readData(int id)
{
enter();
while (writing || write_wait > 0)
{
wait();
}
read_cnt++;
std::cout << "[读者" << id << "] 读 data=" << data << "\n";
leave();
std::this_thread::sleep_for(std::chrono::milliseconds(50));
enter();
read_cnt--;
if (read_cnt == 0)
{
broadcast();
}
leave();
}
void writeData()
{
enter();
write_wait++;
while (writing || read_cnt > 0)
{
wait();
}
write_wait--;
writing = true;
data += 20;
std::cout << "[写者] 修改 data=" << data << "\n";
leave();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
enter();
writing = false;
broadcast();
leave();
}
};
void testWriteFirstRW()
{
std::cout << "\n==========【管程|写者优先 读者-写者】==========\n";
WriteFirstRWMonitor rw;
auto reader = [&](int id){
for(int i = 0; i < 2; ++i)
{
rw.readData(id);
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
};
auto writer = [&](){
for(int i = 0; i < 2; ++i)
{
rw.writeData();
std::this_thread::sleep_for(std::chrono::milliseconds(80));
}
};
std::thread r1(reader,1);
std::thread r2(reader,2);
std::thread w(writer);
r1.join();
r2.join();
w.join();
}
//=========================
// 4. 管程实现:哲学家就餐(奇偶策略防死锁)
//=========================
class DiningMonitor : public Monitor
{
private:
const int N;
std::vector<bool> fork;
public:
explicit DiningMonitor(int num) : N(num), fork(num, true) {}
void takeFork(int id)
{
enter();
int left = id;
int right = (id + 1) % N;
if(id % 2 == 0)
{
while(!fork[left]) wait();
fork[left] = false;
while(!fork[right]) wait();
fork[right] = false;
}
else
{
while(!fork[right]) wait();
fork[right] = false;
while(!fork[left]) wait();
fork[left] = false;
}
leave();
}
void putFork(int id)
{
enter();
int left = id;
int right = (id + 1) % N;
fork[left] = true;
fork[right] = true;
broadcast();
leave();
}
};
void testDiningPhilosopher()
{
std::cout << "\n==========【管程|哲学家就餐(防死锁)】==========\n";
const int PHIL_NUM = 5;
DiningMonitor mon(PHIL_NUM);
auto philosopher = [&](int id){
for(int i = 0; i < 2; ++i)
{
std::cout << "[哲学家" << id << "] 思考\n";
std::this_thread::sleep_for(std::chrono::milliseconds(70));
mon.takeFork(id);
std::cout << "[哲学家" << id << "] 就餐\n";
std::this_thread::sleep_for(std::chrono::milliseconds(100));
mon.putFork(id);
}
};
std::vector<std::thread> phs;
for(int i = 0; i < PHIL_NUM; ++i)
phs.emplace_back(philosopher, i);
for(auto &t : phs) t.join();
}
//=========================
// 5. 管程实现:睡眠理发师
//=========================
class BarberMonitor : public Monitor
{
private:
const int chairs;
int waiting;
std::atomic<bool> shutdown;
public:
explicit BarberMonitor(int seat) : chairs(seat), waiting(0), shutdown(false) {}
bool customerArrive()
{
enter();
bool accept = false;
if (waiting < chairs)
{
waiting++;
accept = true;
}
leave();
return accept;
}
void barberWork()
{
while (!shutdown.load())
{
enter();
while (waiting == 0 && !shutdown.load())
{
wait();
}
if (shutdown.load())
{
leave();
break;
}
waiting--;
std::cout << "[理发师] 开始理发\n";
leave();
std::this_thread::sleep_for(std::chrono::milliseconds(120));
std::cout << "[理发师] 理发完成\n";
}
std::cout << "[理发师] 今日营业结束\n";
}
void stopShop()
{
enter();
shutdown.store(true);
broadcast();
leave();
}
};
void testSleepBarber()
{
std::cout << "\n==========【管程|睡眠理发师】==========\n";
const int WAIT_CHAIR = 4;
BarberMonitor mon(WAIT_CHAIR);
auto barber_func = [&](){
mon.barberWork();
};
auto customer_func = [&](int id){
bool ok = mon.customerArrive();
if(ok)
{
std::cout << "[顾客" << id << "] 等待理发\n";
mon.signal();
}else{
std::cout << "[顾客" << id << "] 座位已满,离开\n";
}
};
std::thread barber_t(barber_func);
std::vector<std::thread> custs;
for(int i = 1; i <= 6; ++i)
{
custs.emplace_back(customer_func, i);
std::this_thread::sleep_for(std::chrono::milliseconds(60));
}
for(auto &t : custs) t.join();
mon.stopShop();
barber_t.join();
}
//=========================
// 6. 管程实现:同步屏障 Barrier
//=========================
class BarrierMonitor : public Monitor
{
private:
int total;
int arrived;
public:
explicit BarrierMonitor(int n) : total(n), arrived(0) {}
void barrierWait()
{
enter();
arrived++;
if (arrived == total)
{
arrived = 0;
broadcast();
}
else
{
wait();
}
leave();
}
};
void testBarrier()
{
std::cout << "\n==========【管程|同步屏障 Barrier】==========\n";
const int THREAD_CNT = 4;
BarrierMonitor bar(THREAD_CNT);
auto worker = [&](int id){
for(int round = 1; round <= 2; ++round)
{
std::cout << "[线程" << id << "] 完成阶段" << round << "任务,等待其他线程\n";
bar.barrierWait();
std::cout << "[线程" << id << "] === 全体就绪,进入下一阶段 ===\n";
}
};
std::vector<std::thread> ths;
for(int i = 1; i <= THREAD_CNT; ++i)
ths.emplace_back(worker, i);
for(auto &t : ths) t.join();
}
//=========================
// 主函数:依次运行全部实验
//=========================
int main()
{
std::cout << "============ 管程(Monitor)综合同步实验 ============\n";
testProducerConsumer();
testReadFirstRW();
testWriteFirstRW();
testDiningPhilosopher();
testSleepBarber();
testBarrier();
std::cout << "\n============ 所有实验执行完毕 ============\n";
return 0;
}
Linux 编译运行命令
g++ monitor_all.cpp -o monitor_all -std=c++11 -pthread
./monitor_all
程序运行结果展示
============ 管程(Monitor)综合同步实验 ============
==========【管程|生产者-消费者】==========
[生产者] 生产:0
[消费者] 消费:0
[生产者] 生产:1
[消费者] 消费:1
[生产者] 生产:2
[消费者] 消费:2
[生产者] 生产:3
[生产者] 生产:4
[消费者] 消费:3
[生产者] 生产:5
[消费者] 消费:4
[生产者] 生产:6
[生产者] 生产:7
[消费者] 消费:5
[消费者] 消费:6
[消费者] 消费:7
==========【管程|读者优先 读者-写者】==========
[读者1] 读取 data = 100
[读者2] 读取 data = 100
[写者] 修改 data = 110
[读者2] 读取 data = 110
[读者1] 读取 data = 110
[写者] 修改 data = 120
==========【管程|写者优先 读者-写者】==========
[读者1] 读 data=100
[读者2] 读 data=100
[写者] 修改 data=120
[读者1] 读 data=120
[读者2] 读 data=120
[写者] 修改 data=140
==========【管程|哲学家就餐(防死锁)】==========
[哲学家0] 思考
[哲学家1] 思考
[哲学家2] 思考
[哲学家3] 思考
[哲学家4] 思考
[哲学家2] 就餐
[哲学家0] 就餐
[哲学家0] 思考
[哲学家2] 思考
[哲学家1] 就餐
[哲学家3] 就餐
[哲学家3] 思考
[哲学家[哲学家2] 就餐
[哲学家1] 思考
0] 就餐
[哲学家4] 就餐
[哲学家1] 就餐
[哲学家3] 就餐
[哲学家4] 思考
[哲学家4] 就餐
==========【管程|睡眠理发师】==========
[顾客1] 等待理发
[理发师] 开始理发
[顾客2] 等待理发
[理发师] 理发完成
[顾客3] 等待理发
[理发师] 开始理发
[顾客4] 等待理发
[理发师] 理发完成
[理发师] 开始理发
[顾客5] 等待理发
[顾客6] 等待理发
[理发师] 理发完成
[理发师] 今日营业结束
==========【管程|同步屏障 Barrier】==========
[线程1] 完成阶段1任务,等待其他线程
[线程2] 完成阶段1任务,等待其他线程
[线程3] 完成阶段1任务,等待其他线程
[线程4] 完成阶段1任务,等待其他线程
[线程3] === 全体就绪,进入下一阶段 ===
[线程3] 完成阶段2任务,等待其他线程
[线程[线程1] === 全体就绪,进入下一阶段 ===
2] === 全体就绪,进入下一阶段 ===
[线程1] 完成阶段2任务,等待其他线程
[线程4] === 全体就绪,进入下一阶段 ===
[线程4] 完成阶段2任务,等待其他线程
[线程2] 完成阶段2任务,等待其他线程
[线程2] === 全体就绪,进入下一阶段 ===
[线程3] === 全体就绪,进入下一阶段 ===
[线程[线程4] === 全体就绪,进入下一阶段 ===
1] === 全体就绪,进入下一阶段 ===
============ 所有实验执行完毕 ============
十、各类同步方案横向对比
| 方案 | 核心原理 | 优点 | 缺点 |
|---|---|---|---|
| Peterson 等软件临界区算法 | 共享变量轮询 | 无需硬件支持 | 仅双线程,功能受限,易出错 |
| TAS/CAS 自旋锁 | CPU 原子指令 | 用户态、低延迟 | 忙等,消耗 CPU |
| 信号量 Semaphore | P/V 原语 | 灵活,兼顾同步 + 互斥 | P/V 分散,极易配对错误引发死锁 |
| 管程 Monitor | 封装资源 + 内置互斥 + 条件变量 | 封装性强,安全性高,代码整洁 | 粒度较重,底层依赖锁与条件变量 |
十一、典型同步故障总结
- 竞争条件 Race Condition:缺少互斥,多个线程同时修改共享变量;
- 死锁:环路等待、资源请求顺序不一致;
- 饥饿:读者优先 RW 中写者长期得不到执行机会;
- 虚假唤醒:条件变量被无理由唤醒;解决方案:永远使用 while 循环判断条件,禁止 if;
- 信号量顺序错误:互斥锁 P 放在同步信号量 P 之前,导致死锁。
十二、实验结论
- 并发程序访问共享资源必须引入同步互斥机制,否则结果不可预测;
- 底层依靠硬件原子指令实现锁;操作系统封装信号量;高级语言进一步封装管程思想;分层递进;
- 简单临界区可选用自旋锁;通用同步场景信号量灵活;大型项目优先采用管程风格封装,降低同步 bug;
- 解决同步问题不仅要保证互斥,还需要考虑进程饥饿、死锁、执行公平性,不能只满足基础正确性。
十三、拓展思考
C++ 标准库std::mutex、std::condition_variable等价于管程内部组件;C++ 没有原生管程关键字,但可以依靠类封装模拟 Monitor。
现代操作系统极少直接暴露信号量给应用层开发;并发框架普遍采用管程思想(对象内部状态保护、成员方法作为唯一访问入口),是工业界主流实践。
十四、实验总结
本实验系统研究了进程并发执行中的同步与互斥问题,通过四种层级的同步方案实现与对比分析,揭示了不同机制的适用场景。实验首先验证了Peterson等纯软件临界区算法的局限性,进而基于原子指令实现了用户态自旋锁(TAS/CAS)。针对经典同步问题(生产者-消费者、读者写者等),分别采用信号量和管程两种范式实现,结果表明:信号量虽灵活但易出现P/V配对错误,而管程通过封装共享数据与操作显著提升了代码安全性与可维护性。实验还揭示了竞争条件、死锁等典型同步故障的成因,强调分层设计思想——硬件原子指令支撑操作系统原语,上层语言抽象管程结构。最终结论指出,工业级并发程序应优先采用管程模式,在保证正确性的同时兼顾执行公平性与资源利用率。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)