4.7 KiB
4.7 KiB
Processes and Threads
Motivation for threads 为什么我们需要线程
- 操作系统必须支持多个任务同时运行。例如,进程、终端或系统维护。
- 操作系统必须支持多个连接同时运行。例如,多用户、Web连接等。
- 进程必须能够同时执行多个任务。例如,一个游戏必须同时有图形、声音与运算逻辑等。
- 操作系统必须同时访问多个硬件,或者至少看起来像是同时访问。你不会希望动鼠标、按键盘时,系统不能访问网络或磁盘。
一些定义:
- Multi-Processing:多个 CPU(核心)
- Multi-Programming:多个作业/进程
- Multi-Threading:多个线程/进程
Concurrency 并发
并发运行两个线程意味着什么?
- 调度器可以自由地,以任意顺序和交错方式,运行线程
- 线程可能会运行至完成,或者以大块或小块的时间片运行
Computes that never finish 永远无法完成的计算
想象如下代码,其中ComputePi()会尝试计算π的最后一位,并将计算过程写入文件pi.txt。
main() {
ComputePI("pi.txt");
PrintClassList("classlist.txt");
}
这个例子中,显然ComputePi是个不可能完成的函数。因此我们完全没有办法print ClassList!程序也无法停止,除非你Ctrl-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核心:

但是如果其中一个线程需要执行IO操作,那么它就会被阻塞,另一个线程就可以继续使用CPU核心:

注: Blocked状态不会直接转为Running状态,必须先转为Ready状态。
How to manage threads 如何管理线程
一个程序默认就含有一个线程。如果想要创建其他线程,我们必须使用一次System Call。
在C语言中,使用pthread_create()函数来创建线程。
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine)(void*), void *arg);
- 创建线程,并以
arg为唯一参数执行start_routine。 return相当于隐式调用pthread_exit。
void pthread_exit(void *value_ptr);
- 终止线程,并使
value_ptr对任何成功的join操作可用
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 Call之上,用户程序之下还有一层Portable OS Library。这个库允许程序员使用同一套API来编写程序。
- C程序猿们会使用
libc,提供很多以某种方式包装的系统调用。
