Introduce
同步的一个例子
临界区:只有一个线程能在同一时间内执行,不允许多个线程同时访问的代码
T3正确,其余都错误
Condition Variable条件变量
如果一个线程拿了锁,但是又依赖于其他线程的操作
允许一个拿了锁的线程临时去释放自己的锁,直到满足条件唤醒
Signal和Broadcast需要区分下

如果需要插入元素时队列是满的,那么需要等队列空出来再插入元素;remove同理
remove()如何实现?
bounded_queue::remove(){
lock.acquire();
while(queue.empty()){
itemAdded.wait(&lock);
}
int item = get_item();
itemRemoved.signal();
lock.release();
return item
}
条件变量只有在拿着锁的时候才会使用,等待的时候会自动release锁,但是被唤醒的时候会重新拿回锁
为什么是while 循环?
使用while循环而不是if主要是为了防止虚假唤醒(spurious wakeup)问题。
- 虚假唤醒的问题:
- 条件变量可能在没有实际signal()调用的情况下唤醒
- 操作系统可能错误地唤醒等待的线程
- 有时一个signal()可能唤醒多个等待的线程
- 如果使用if的潜在问题:
// 使用if的问题代码
if (queue.full()) {
itemRemoved.wait(&lock); // 如果这里发生虚假唤醒
}
add_item(item); // 队列可能仍然是满的!- while循环的保护机制:
// 安全的实现
while (queue.full()) {
itemRemoved.wait(&lock); // 即使发生虚假唤醒
// 会再次检查条件,确保队列真的有空间
}
add_item(item); // 确保队列一定有空间- while循环的优势:
- 每次被唤醒时都会重新检查条件
- 确保条件满足才会继续执行
- 提供了额外的安全检查层
- 使代码更加健壮和可靠
使用while循环是一种防御性编程的实践,可以确保在并发环境下程序的正确性。
Semaphores 信号量
P操作:试图将值-1,如果值=0,那么就等到非零
V操作:试图将值+1,并唤醒等待的P
原子操作
有两个P操作,只有一个可以降为0,另一个只有等到一个V操作之后才能P
如何用信号量去实现一个锁?拿锁P,释放锁V
A线程想等到B线程执行完再执行?
- 信号量设置为0,A线程P一下,B线程执行完后会V一下,那么等到V之后才会执行A线程的P
Producer - Consumer with a Bounded Buffer
如何用信号量去解决生产消费者问题?

限制
- 如果队列空,消费者必须等到生产者填入buffer
- 如果队列满,生产者必须等到消费者取走buffer
- 只有一个线程能在一个时间内操作buffer queue
对于解决方案的讨论
为什么消费者和生产者的操作不是对称的?

- 生产者和消费者的操作顺序是不对称的,这是由于它们的功能不同:
- 生产者:先检查空槽位(emptySlots.P()),再增加已占用槽位(fullSlots.V())
- 消费者:先检查已占用槽位(fullSlots.P()),再增加空槽位(emptySlots.V())
- 这种不对称设计确保了资源的正确管理和同步
P操作的顺序很重要,必须先获取资源信号量,再获取互斥锁
如果顺序反过来(如图中红色标注的错误顺序)可能导致死锁:
- 如果先获取mutex,一个线程可能在等待资源时阻塞住mutex
- 其他线程因无法获取mutex而无法释放资源
- 形成死锁
What if we have 2 producers or 2 consumers?(如果有多个生产者或消费者呢?)
- 这个实现可以支持多个生产者和消费者
- mutex确保了对缓冲区的互斥访问
- 信号量机制确保了资源的正确同步
- 多个生产者/消费者的情况下,该方案仍然可以正确工作,不需要修改代码
