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

4.7 KiB
Raw Permalink Blame History

Processes and Threads

Motivation for threads 为什么我们需要线程

  • 操作系统必须支持多个任务同时运行。例如,进程、终端或系统维护。
  • 操作系统必须支持多个连接同时运行。例如多用户、Web连接等。
  • 进程必须能够同时执行多个任务。例如,一个游戏必须同时有图形、声音与运算逻辑等。
  • 操作系统必须同时访问多个硬件,或者至少看起来像是同时访问。你不会希望动鼠标、按键盘时,系统不能访问网络或磁盘。

一些定义:

  • Multi-Processing多个 CPU核心
  • Multi-Programming多个作业/进程
  • Multi-Threading多个线程/进程

Concurrency 并发

并发运行两个线程意味着什么?

  • 调度器可以自由地,以任意顺序和交错方式,运行线程
  • 线程可能会运行至完成,或者以大块或小块的时间片运行

MultiProcessing vs MultiProgramming

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核心: Thread Scheduling Without IO

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

注: 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_ptrNULL,则终止线程传递给 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 你会注意到System Call之上用户程序之下还有一层Portable OS Library。这个库允许程序员使用同一套API来编写程序。

  • C程序猿们会使用libc,提供很多以某种方式包装的系统调用。