In [1]:
from mosaic import *
OS2023(5)
5. 多处理器编程:从入门到放弃¶
Changelog & 反馈
- 翻车的 sum.py: 漏了 store 之后的
sys_sched()
,这样 store-load 就总是原子执行- 并发:总是相信自己错了
- 想起了相机有曝光补偿,不用后期折腾了
- Clarification: 网页上的状态机仅是 “一次执行”, 请下载 mosaic.py 运行
背景回顾:“操作系统玩具” 给出了理解操作系统的新视角:操作系统是状态机的管理者。因为在 sys_sched()
之后操作系统拥有随机选择状态机执行的权力,因此也带来了并发性。操作系统是世界上最早的并发程序。
本讲内容:多处理器编程:从入门到放弃:
- 入门:多线程编程库
- 放弃:原子性、可见性、顺序
In [2]:
slideshow('5.1')
In [3]:
model('m/print.py', check=True)
In [4]:
demo('hello', 'c/hello.c', libs=['thread.h'])
In [5]:
demo('stack-probe', 'c/stack-probe.c', libs=['thread.h'])
In [6]:
slideshow('5.2')
In [7]:
demo('alipay', 'c/alipay.c', libs=['thread.h'])
In [8]:
demo('sum', 'c/sum.c', libs=['thread.h'])
In [9]:
slideshow('5.3')
In [10]:
demo('sum-opt', 'c/sum.c', libs=['thread.h'])
In [11]:
slideshow('5.4')
In [12]:
model('m/store-load.py', check=True)
In [13]:
demo('store-load', 'c/store-load.c', libs=['thread.h'])
Take-away Messages¶
在一个简化的模型中,多线程/多进程程序就是 “状态机的集合”,每一步选一个状态机执行一步。然而,真实的系统可能带来一些复杂性:
- 指令/代码执行原子性假设不再成立
- 程序的顺序执行假设不再成立
- 多处理器间内存访问无法即时可见
然而,人类本质上是物理世界 (宏观时间) 中的 “sequential creature”,因此我们在编程时,也 “只能” 习惯单线程的顺序/选择/循环结构,真实多处理器上的并发编程是非常具有挑战性的 “底层技术”,例如 Ad hoc synchronization 引发了很多系统软件中的 bugs。因此,我们需要并发控制技术 (之后的课程涉及),使得程序能在不共享内存的时候并行执行,并且在需要共享内存时的行为能够 “容易理解”。
课后习题/编程作业¶
1. 阅读材料¶
教科书 Operating Systems: Three Easy Pieces:
- 第 25 章 - Dialogue on Concurrency
- 第 26 章 - Concurrency and Threads
- 第 27 章 - Thread API
注意:我们的课程和教科书有较大的重叠,但教科书提供了许多授课时间比较难以花时间讲清楚的细节,因此仔细阅读教科书同样重要。
2. 编程实践¶
在你的 Linux 中运行课堂上的代码示例。同学们也可以打开 thread.h:它使用起来很简单 (直接创建线程即可),但实现也很有趣。例如,pthreads 线程接受一个 void *
类型的参数,且必须返回一个 void *
。我们用 wrapper function 的方法解决这个问题:所有的线程的实际入口都是名为 wrapper 的函数,它会在内部调用线程的 entry 函数。
void *wrapper(void *arg) {
struct thread *thread = (struct thread *)arg;
thread->entry(thread->id);
return NULL;
}
此外,代码中还有一些可以学习的编程技巧,例如 ctor 和 dtor 等。