Files
my-notes/UCB-CS162/L03-Processes-and-Threads.md

128 lines
4.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`,提供很多以某种方式包装的系统调用。