Skip to content

线程

约 1145 个字 2 张图片 预计阅读时间 4 分钟

简介

为了提高系统响应性,需要将操作分的尽可能小,但是为了高效的执行,代码应该较大块地执行,更准确地说,我们希望能够调度更小的操作,这些枯燥的工作可以留给OS去完成,这就是线程。线程可以理解成更轻量级的进程,进程是资源分配的最小单位,而线程是调度的基本单位。

线程相比进程有如下优点:

  1. 响应性(Responsiveness):交互性应用
  2. 资源共享(Resource Sharing):分配给代码和数据的内存可以被共享
  3. 经济(Economy):创建进程的开销更大
  4. 利用多进程架构(Utilization of MP Architectures):多线程增加了并发度

Concurrency vs. Parallelism

并行一般是指真的同时执行,比如多核CPU或者分布式系统,而并发可以是真的并行,但更侧重有多个任务轮流执行,在单核CPU上也可以并发。

线程可以共享进程的部分资源,包括代码、数据和文件等,但每个线程都有自己的寄存器和栈,栈本质是同一个,但是不同线程的sp指针的位置不同,只要找到地址就可以把值取出。

线程管理

用户线程 vs. 内核线程

  • 用户线程的管理由用户态线程库完成,比如Java threads
  • 内核线程由内核支持,现代OS基本都实现了内核线程
  • 如果只有用户线程,那么实际上OS调度的还是进程

多线程基本模型

  • many to one的模型将多个用户线程映射到一个内核线程,线程管理非常高效,但是在有线程进行系统调用时,其它线程也会被阻塞,原因是OS并不知道有很多个线程,它一次只会调度一个线程
  • one to one的模型将一个用户线程映射到一个内核线程,有更高的并发,但是创建线程的开销会比较高
  • many to many的模型将多个用户线程映射到多个内核线程,折中的方式使它更加灵活

Two level 模型

类似于many to many的模型,不过它同时也允许一个用户态线程被绑定到一个内核线程上。

相关事务

forkexec

一个线程调用了fork创建了一个新进程,是会复制所有线程,还是只复制调用它的线程,这个取决于操作系统,一些Unix的系统会同时拥有两种实现。

exec会替代整个进程

线程取消

在一个线程完成之前终止它,有两种通用的方法:

  1. Asynchronous cancellation:立刻终止目标线程
  2. Deferred cancellation:允许周期性检查标志位,从而取消目标线程

信号控制

  • 信号(singnals)在unix系统中用来通知一个进程特定事件发生了
  • 一个信号控制器来处理信号,要么同步要么异步
  • 信号由特定的事件产生,被传送给特定的进程,最终被处理
  • 信号可以选择发给信号应用的线程、进程中的所有线程、进程中的特定线程或者是用一个专门的线程来接收进程所有信号。

线程池

预先创建一系列的线程备用(类似于内存池,避免每次都去系统调用)。优点如下:

  1. 通常使用已有的线程比创建新的线程要稍快
  2. 使系统中线程的数量限制在在池大小内

线程数据

TLS(Thread Local Storage)允许每个线程都有自己专属的数据,和静态变量很像,但是对每个线程来说是独一无二的,在你没有控制线程创建的情况下很有用

调度器活动

many to many和two level都需要通知内核以维护恰当的内核线程数量,在许多系统中LWP(light-weight-process)充当了用户级线程和内核线程之间的桥梁,可以把它理解为比进程开销小,但比纯粹的用户级线程更直接地得到内核支持的执行单元。

Scheduler Activations提供了upcall机制能够从kerenl将信息传递到线程库,这种通信使得应用程序能够维持正确数量的内核线程,当一个应用程序线程即将阻塞时就会触发一个upcall