Update: CS162 L03 partly finished

This commit is contained in:
2026-05-02 02:25:04 +08:00
parent a623e36b6f
commit 64e47032d4
5 changed files with 126 additions and 1 deletions

View File

@@ -1,2 +1,127 @@
# Processes and Threads
##
## Motivation for threads 为什么我们需要线程
- 操作系统必须支持多个任务同时运行。例如,进程、终端或系统维护。
- 操作系统必须支持多个连接同时运行。例如多用户、Web连接等。
- 进程必须能够同时执行多个任务。例如,一个游戏必须同时有图形、声音与运算逻辑等。
- 操作系统必须同时访问多个硬件,或者至少看起来像是同时访问。你不会希望动鼠标、按键盘时,系统不能访问网络或磁盘。
一些定义:
- Multi-Processing多个 CPU核心
- Multi-Programming多个作业/进程
- Multi-Threading多个线程/进程
### Concurrency 并发
**并发**运行两个线程意味着什么?
- 调度器可以自由地,以任意顺序和交错方式,运行线程
- 线程可能会运行至完成,或者以大块或小块的时间片运行
![MultiProcessing vs MultiProgramming](images/L03-MultiProcessing-vs-MultiProgramming.png)
### Computes that never finish 永远无法完成的计算
想象如下代码其中ComputePi()会尝试计算π的最后一位并将计算过程写入文件pi.txt。
```C
main() {
ComputePI("pi.txt");
PrintClassList("classlist.txt");
}
```
这个例子中显然ComputePi是个不可能完成的函数。因此我们完全没有办法print ClassList程序也无法停止除非你Ctrl-C或者关闭电脑。
但是使用线程,我们可以这么做:
```C
main() {
create_thread(ComputePI, "pi.txt");
create_thread(PrintClassList, "classlist.txt");
}
```
这样即便ComputePI永远无法完成我们也可以得到ClassList。
### IO / Compute Overlap
这是一个各种操作的时间表:
| Operation | Time |
| :--- | :--- |
| L1 cache reference | 0.5 ns |
| Branch mis-predict | 5 ns |
| L2 cache reference | 7 ns |
| Mutex lock/unlock | 25 ns |
| Main memory reference | 100 ns |
| Compress 1K bytes with Zippy | 3,000 ns |
| Send 2K bytes over 1 Gbps network | 20,000 ns |
| Read 1 MB sequentially from DDR2 memory | 250,000 ns |
| Round trip within same datacenter | 500,000 ns |
| Disk seek on HDD | 10,000,000 ns |
| Read 1 MB sequentially from HDD disk | 20,000,000 ns |
| Send packet CA->Netherlands->CA | 150,000,000 ns |
Disk Seek硬盘寻址等操作显然需要很久比如10ms。我们显然不希望CPU的一个核心等待这么久不做任何计算。
## Introduction to threads 线程简介
线程有三个状态。如下:
- Running 运行态: 线程正在使用CPU的时间片
- Ready 就绪态: 线程准备好运行了但还没有上CPU
- Blocked 阻塞态: 线程不能继续运行
我们可以基于此设计调度器。例如如果两个线程都不需要IO它们很可能会轮流使用同一个CPU核心:
![Thread Scheduling Without IO](images/L03-Thread-Scheduling-Without-IO.png)
但是如果其中一个线程需要执行IO操作那么它就会被阻塞另一个线程就可以继续使用CPU核心
![Thread Scheduling With IO](images/L03-Thread-Scheduling-With-IO.png)
注: Blocked状态不会直接转为Running状态必须先转为Ready状态。
### How to manage threads 如何管理线程
一个程序默认就含有一个线程。如果想要创建其他线程我们必须使用一次System Call。
在C语言中使用```pthread_create()```函数来创建线程。
```C
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine)(void*), void *arg);
```
- 创建线程,并以 `arg` 为唯一参数执行 `start_routine`。
- `return` 相当于隐式调用 `pthread_exit`。
```C
void pthread_exit(void *value_ptr);
```
- 终止线程,并使 `value_ptr` 对任何成功的 `join` 操作可用
```C
int pthread_join(pthread_t thread, void **value_ptr);
```
- 挂起调用线程的执行,直到目标线程终止。
- 返回时,若 `value_ptr` 非 `NULL`,则终止线程传递给 `pthread_exit()` 的值将存储在 `value_ptr` 指向的位置。
#### `void *(*start_routine)(void*)`是什么玩意?
我们从内向外看这个东西。
`*start_routine` 是一个函数指针,指向一个函数,这个函数接受一个 `void*` 类型的参数,并返回一个 `void*` 类型的值。
## System Calls
System Call 接口隔开了用户程序与操作系统内核。
这使得系统设计看起来像一个沙漏而System Call正式沙漏中间最窄的地方
如图所示:
![System is the interface between user and kernel](images/L03-System-Call-In-OS.png)
你会注意到System Call之上用户程序之下还有一层Portable OS Library。这个库允许程序员使用同一套API来编写程序。
- C程序猿们会使用`libc`,提供很多以某种方式包装的系统调用。

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 827 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB