Operating System Chapter4 理解并发程序执行
Operating System
$Nanjing\ University\rightarrow Yanyan\ Jiang\newline$
晚上看的,Peterson算法好™️的👨,证明等过两天有时间给看了
理解并发程序执行
复习
- 一般程序执行
线程的栈帧会用一个list
来存放
- 多线程
T1,T2随机来回转换执行
- 并发程序 = 多个执行流、共享内存的状态机
画状态机理解并发程序
互斥:保证两个线程不能同时执行一段代码
- 插入 “神秘代码”,使得 sum.c (或者任意其他代码) 能够正常工作
|
|
- 可以通过
__sync_synchronize();
来保证原子操作
|
|
__sync_synchronize();
是GCC内置函数的一种,用于在编写多线程程序时确保在内存操作之前和之后的指令都不会被重排。具体来说,这个函数是一个内存栅栏,用于告诉编译器不要把本条指令前面和后面的内存操作顺序交换,也就是防止编译器进行指令重排,保证在这个函数之前和之后的内存操作按照代码中的顺序执行。
这个函数在实现多线程锁、原子操作等场景中经常被使用,以确保线程间的同步和一致性。
失败的尝试
|
|
-
原因
- 看到的状态到真正做下一件事之间的状态是否被人改了?(看到的东西仅仅只是一个历史,离做还有几个周期,而做是根据这个“历史”的状态来决定的)$\Longrightarrow$ 和人眼看着东西对比,人眼是一直在看的,而CPU并行执行程序的时候,看这个指令执行完了之后可能CPU会去执行另一个线程的几条指令,这种情况下相当于这个人看完后闭上眼睛等了几秒,然后根据几秒前所看的东西来判断自己要干啥,可是这个东西在前几秒可能已经让另一个线程的某些指令改过了
- 处理器默认不保证 load + store 的原子性(单操作可以保证)
-
一种失败的情况
|
|
正确性不明的奇怪尝试 (Peterson
算法)
-
A 和 B 争用厕所的包厢
-
想进入包厢之前,A/B 都要先举起自己的旗子
- A 确认旗子举好以后,往厕所门上贴上 “B 正在使用” 的标签
- B 确认旗子举好以后,往厕所门上贴上 “A 正在使用” 的标签
-
然后如果对方的旗子举起来,且门上的名字不是自己,等待
- 否则可以进入包厢
-
出包厢后,放下自己的旗子
-
示例代码:peterson-simple.c
|
|
有关上面的原子操作:
这段代码定义了两个原子变量
nested
和count
,并实现了一个临界区critical_section()
。在
critical_section()
中,首先通过调用atomic_fetch_add(&count, 1)
将count
原子变量的值加 1 并返回当前的值,保存在cnt
中。然后通过调用
atomic_fetch_add(&nested, 1)
将nested
原子变量的值加 1,同时检查原子变量nested
是否为 0。由于nested
的初始值为 0,因此这里可以通过assert
断言来验证。最后通过调用
atomic_fetch_add(&nested, -1)
将nested
原子变量的值减 1。整个
critical_section()
实现了一个简单的临界区,其中nested
原子变量用于保证临界区同时只能被一个线程访问,而count
原子变量用于记录临界区的进入次数。
在给
nested
这个atomic_int
对象赋初值为 0 的情况下,断言assert(atomic_fetch_add(&nested, 1) == 0)
是可以通过的。这是因为
atomic_fetch_add()
函数是原子的,它会将nested
的值加 1,并返回增加前的值。在这个代码中,nested
初始值为 0,然后通过atomic_fetch_add(&nested, 1)
将其增加为 1,并返回增加前的值 0。因此,断言assert(atomic_fetch_add(&nested, 1) == 0)
会成功通过,因为atomic_fetch_add()
返回的值与断言中的比较值相等。需要注意的是,
atomic_int
是 C++ 标准库提供的原子类型,用于在多线程环境下进行原子操作,确保线程安全性。在多线程环境下,原子操作是不会被中断的,因此可以保证atomic_int
类型对象的操作不会发生竞态条件等问题。但仍然需要谨慎使用,并根据具体情况考虑是否需要使用其他同步机制,如互斥锁、条件变量等。
Peterson’s Protocol Verified 🎖
原来证明和判断可以用状态🌳来证明(和全数学相比可能不严谨,但是从CS的角度来说够用了)
先😴,回来再看
我们 (在完全不理解算法的前提下) 证明了 Sequential 内存模型下 Peterson’s Protocol 的 Safety。它能够实现互斥。
- “Myths about the mutual exclusion problem” (IPL, 1981,
Peterson
算法论文,终于给了一个对于我们来说理解起来相对容易的一个算法) - 之前有关锁的一些复杂算法:dekker.py
|
|
- 一些现状
- 今天有非常坚 (内) 实 (卷) 的理论体系
- 小心编译器和多处理器硬件(编译器优化,CPU指令流水线优化)
- peterson-barrier.c(改良版,防止编译器优化造成的结果和预期不符)
|
|
- 课后思考:哪些 barrier 是多余的吗?
|
|
(自动) 画状态机理解并发程序
体育课累歇逼了,晚上给看完
-
并发算法的设计困境
-
不敢不画:谁知道有什么奇怪情况会发生?
-
不敢乱画:画错了就都完了
-
-
解决方法
- 让电脑帮我们画(因为画状态机就是一个机械的事情)
|
|
-
$Model$ $\Longrightarrow$ $Input$
-
$checker$ $\Longrightarrow$ 遍历所有状态
-
通过一些class来说明你的并发算法:mutex-bad.py, peterson-flag.py, dekker.py
|
|
|
|
- 使用例
|
|
-
后面跟着model的参数脚本即可
-
为什么输出模式这么反人类 $\Longrightarrow$ 因为输出是给程序看的,后面的python脚本可以写一些lambda表达式来提取信息 $\Longrightarrow$ 加到后期的工具visualize.py,直接可视化(很像之前zweix大佬的jyyslide-md)的markdown转html的感觉很像
-
安装相关模块,除了pip的以外Linux的机器上要安装graphviz
|
|
- 然后通过管道通信和重定向
|
|
代码导读:Python Generator
死循环也能返回?
|
|
yield
让死循环返回,但又不是完全返回
-
这里就模仿了线程的执行使用
yield
的特性
Model Checker: 实现
|
|
|
|
Model Checker: 实现 (cont’d)
什么是状态空间?
- 所有可能的状态机执行序列
BFS
生成,合并重复状态
|
|
Model Checking 和工具的故事
Model checking is a method for formally verifying finite-state systems——只要能为系统建立模型,就能用 prove by brute-force 证明正确/找到错误。
Model checker 的一切就是状态机!
- Safety: 红色的状态不可到达
- G(V,E) 上的可达性问题
- (Strong) Liveness: 从任意状态出发,都能到达绿/蓝色状态
- G(V,E) 上的什么问题?
- 如何展示这个状态机?
- 如何能避免无效的探索?
更多的 Model Checker
真实程序的状态空间太大?
- Model checking for programming languages using VeriSoft (POPL'97, 第一个 “software model checker”)
- Finding and reproducing Heisenbugs in concurrent programs (OSDI'08, Small Scope Hypothesis 🪳🪳🪳)
- Using model checking to find serious file system errors (OSDI'04, Best Paper 🏅,可以用在不并发的系统上)
不满足于简单的内存模型?
- VSync: Push-button verification and optimization for synchronization primitives on weak memory models (ASPLOS'21, Distinguished Paper 🏅)
总结
本次课回答的问题
- Q: 如何理解各种并发程序?
Take-away message
- 并发程序 = 状态机
- 线程共享内存
- 每一步非确定选择线程执行
- 画状态机就对了
- 当然,用工具帮你画 (model checker)
声明:本文章引用资料与图像均已做标注,如有侵权本人会马上删除