from mosaic import *
OS2023(9)
9. 并发控制:同步 (1)¶
Changelog & 反馈
- Model Checker 在代码后显示所有可能的输出 (过往课堂示例也已更新,体验提升明显)
- 修复了 L0 Online Judge 的小问题
- 上线 M2 的 Online Judge 和 L1 实验
背景回顾:我们已经了解如何通过 “不可优化、保证顺序” 的原子指令实现自旋锁,以及借助操作系统 (系统调用) 实现线程的睡眠,从而不致于出现 CPU 空转的浪费。然而,互斥并不总是能满足多个并发线程协作完成任务的需求,例如大家试着在完成 Minilab 时应该已经遇到了一些困难。如何能便捷地让共享内存的线程协作以共同完成计算任务?
本讲内容:并发控制:同步
- 同步问题的定义
- 生产者-消费者问题
- 条件变量
slideshow('9.1')
demo('pc-mutex', 'c/pc-mutex.c', libs=['thread.h', 'thread-sync.h'])
slideshow('9.2')
demo('pc-cv', 'c/pc-cv.c', libs=['thread.h', 'thread-sync.h'])
model('m/cv-if.py', check=True)
demo('pc-cv-while', 'c/pc-cv-while.c', libs=['thread.h', 'thread-sync.h'])
model('m/cv-broadcast.py', check=True)
编写正确的并发程序并非易事,即便是资深的系统程序员 (包括 Linux 内核开发者) 都无法避免在代码中引入并发 bug。压力测试、模型检验都是帮助我们提升对并发程序正确性信心的手段。除此之外,防御性地编程 (例如写出尽可能简单、正确性明了的代码) 也是至关重要的。
slideshow('9.3')
demo('fish', 'c/fish.c', libs=['thread.h', 'thread-sync.h'])
上面用 ASCII 字符显示图形的方式称为 “ASCII Art”,在终端时代颇为风靡。以下是 ChatGPT 为我们生成的操作系统架构的 ASCII Art:
+---------------------+
| Application |
| Layer |
+---------------------+
| Operating System |
| Layer |
+---------------------+
| Hardware Abstraction|
| Layer |
+---------------------+
| Hardware |
+---------------------+
随着 AIGC 时代的到来,这种 “传统艺能” 似乎也要消失了。
Take-away Messages¶
同步的本质是线程需要等待某件它所预期的事件发生,而事件的发生总是可以用共享状态的条件来表达。并且在这个条件被满足的前提下完成一些动作:
WAIT_UNTIL(cond) with (mutex) {
// cond 在此时成立
work();
}
计算机系统的设计者提供了条件变量的机制模仿这个过程,它与互斥锁联合使用:
-
cond_wait(cv, lk)
释放互斥锁lk
并进入睡眠状态。注意被唤醒时,cond_wait
会重新试图获得互斥,直到获得互斥锁后才能返回。 -
cond_signal(cv)
唤醒一个在cv
上等待的线程 -
cond_broadcast(cv)
唤醒所有在cv
上等待的线程
我们也很自然地可以用 wait + broadcast 实现 WAIT_UNTIL
,从而实现线程之间的同步。
课后习题/编程作业¶
1. 阅读材料¶
教科书 Operating Systems: Three Easy Pieces:
- 第 29 章 - Locked Data Structures
- 第 30 章 - Condition Variables
教科书详细解释了条件变量错误使用的案例。因为条件变量自身设计的缺陷 (库函数希望 API 保持简单,而不是唤醒 “满足某些条件的线程”),就带来了 cond_signal
使用上的不便。作为折衷,我们推荐 cond_broadcast
的 “万能” 同步方法。
2. 编程实践¶
运行示例代码并观察执行结果。建议大家先不参照参考书,通过两个信号量分别代表 Tproduce 和 Tconsume 的唤醒条件实现同步。
3. 实验作业¶
开始 M2 和 L1 实验作业。