🧠 背景:为什么会有 PV?
类比:内存(生产者) 和 CPU(消费者)
-
内存 / IO / 磁盘 / 网络下载 → 不断“生产数据”
-
例如:读取文件、下载视频、从数据库加载信息
-
-
CPU → 负责“消费数据”
-
例如:处理数据、解码、渲染画面、计算结果
-
👉 由于生产和消费速度可能存在差异(如内存大、CPU处理慢),需要一个缓冲区(如缓存、队列)进行协调。
🔧 三个基本模板
1. 信号量
-
定义:表示当前可用资源的个数。
2. 同步:控制先后顺序
-
核心:信号量初始设为 0;
V
操作释放信号量,进程 B 才能有资源继续执行。 -
问题:如果信号量初始值为 0,进程 A 和进程 B 都会被阻塞吗?
-
回答:是的,这样可以保证进程 A 和 B 的先后顺序。
-
3. 互斥:保证一个进程对资源的访问
-
核心:使用
P
和V
操作夹住临界区(临界资源存放点)。
进程A; mutex=1
P(互斥信号量); mutex--; 0;
临界区;
V(互斥信号量) mutex++; 1;进程B;
P(互斥信号量);
临界区;
V(互斥信号量)
🏭 生产者-消费者模型
✅ full
是什么意思?
-
定义:在生产者-消费者模型中,
full
是一个信号量,用来表示当前缓冲区中“已经存了多少个产品”,也可以理解为“可供消费者消费的数据数量”。
✅ mutex
是什么?
-
定义:
mutex
是 mutual exclusion(互斥)的缩写,表示一次只允许一个线程/进程访问共享资源。用于保护临界区。
🔄 同步与互斥关系
同步关系
-
生产者先生产出一个产品 →
V(full)
,消费者才能消费一个产品 →P(full)
。 -
消费者从缓冲区取来产品 →
V(empty)
,释放一个空位,生产者才能继续生产 →P(empty)
。
互斥关系
-
使用
P(mutex)
加锁缓冲区,V(mutex)
释放缓冲区,确保缓冲区的互斥访问。
❓ 常见问题解答
问题:缓冲区初始为空,empty = n
(如 10 个空位),为什么还需要等待消费者“消费”后产生空位?不是一开始就很多空位了吗?
-
答案:
-
✅ 一开始当然不需要等消费者,可以直接放数据进去!
-
🛑 但如果生产得太快,把缓冲区塞满了(
empty == 0
),就必须等消费者先消费一个产品(释放一个空位)才能继续生产。
-
🧑💻 生产者代码解释
-
生产:在自己线程内处理好数据(不影响别人)。
-
P(empty)
:检查是否有空位(资源控制)。 -
P(mutex)
:进入缓冲区前加锁(互斥控制)。 -
放入缓冲区。
-
V(mutex)
:解锁。 -
V(full)
:通知消费者“有东西可以用了”。
👩💻 消费者代码解释
-
P(full)
:等待是否有产品可取。 -
P(mutex)
:加锁,准备访问缓冲区。 -
取产品:真正的“消费”动作。
-
V(mutex)
:解锁。 -
V(empty)
:通知生产者释放了一个空位。 -
消费:转到自己的线程输出数据。
读者-写者(互斥)
📖 读者-写者问题(互斥)
问题:为什么 mutex
需要夹住 count
,不是允许多个读者同时读吗?
-
回答:是的,多个读者可以同时读,但是
count
是一个“全局变量”,多个线程同时修改它会出问题,所以必须用mutex
来保护它!
🔄 同步与互斥关系
同步关系
-
读者读完后,解锁并将
count--
。当count
为 0 时,表示最后一个读者离开,此时写者可以开始写。 -
简单来说:第一个读者来关写者的门,最后一个读者来开写者的门。
互斥关系:
互斥关系
-
count
的初始值为 0,表示没有读者在读。 -
如果有写者正在写,
rw = 0
,读者会被阻塞,等待写者写完。 -
如果没有写者写,
rw = 1
,第一个读者执行P(rw)
,然后继续执行。
❓ 常见问题解答
问题:如果第一个读者还没执行到 V(mutex)
,第二个读者就进来了,会怎么样?第二个还能继续吗?
-
答案:
-
❌ 第二个读者进不来,它会被阻塞在
P(mutex)
这里,直到第一个读者执行V(mutex)
,它才能继续往下执行。 -
❗ 虽然读者可以“同时读”,但它们更新计数器
count
时一定是串行的、有序的、安全的!
-
相对写优先(有限)
设置P(w)V(w):
1:当读进程先来时候,执行P(W),W=0,写进程堵塞,只有当读进程释放了,写进程才能进来。
2:当写进程先来时候,执行P(W),W=0,读进程堵塞,只有当写进程释放了,读进程才能进来。
哲学家用餐(都会有几率导致死锁)
限制n-1哲学家就餐
使用信号量(semaphore count=4)限制同时就餐的哲学家数量为4个,理论上想避免所有5个哲学家同时拿筷子。但如果4个哲学家分别拿了一根筷子(例如,0号拿0筷子,1号拿1筷子,依此类推),第5个哲学家无法进入就餐,而已进入的4个哲学家仍在等待另一根筷子,依然会死锁。
仅当左右筷子可用时候,才会吃饭。
使用mutex紧紧夹住拿筷子的动作
每个哲学家都先试图拿左筷子(P(chopsticks[i])),再拿右筷子(P(chopsticks[(i+1)%5]))。如果所有哲学家同时拿左筷子,就会形成循环等待(每个人都拿着一根筷子,等待另一根),导致死锁。
奇数号先拿左筷子。偶数号拿右筷子
哲学家编号为:1 ~ 5,筷子编号也为 1 ~ 5
每位哲学家要吃饭,必须拿 左手边和右手边的筷子:
-
哲学家 i 的左手筷子编号:i
-
哲学家 i 的右手筷子编号: (i % 5) + 1
这里试图通过让一个哲学家(if(i%2==0))先拿左筷子,其他人先拿右筷子来打破对称性。但如果所有哲学家同时进入循环,依然可能出现循环等待。例如,编号为偶数的哲学家拿左筷子,编号为奇数的拿右筷子,最终还是会形成死锁。
ChatGPT链接:https://chatgpt.com/share/680274c9-faec-800c-8a90-253e36512386