前言
线程,具有进程的某些性质,又称轻量级进程。引入线程的原因:
- 应用需要——对于一个单线程的进程,它在完成一项任务时,那么就不能执行其他任务
- 开销的考虑——进程的相关操作(创建、撤消、进程间通信、切换等)需要内核的介入,时间空间开销大(系统调用、上下文切换),而线程的相关操作开销要小
- 性能的考虑——对于多CPU,需要大量计算和大量IO处理,多线程可以允许计算和IO重叠处理,从而加快应用程序的执行速度
线程模型
进程是资源的拥有者和CPU的调度单位,线程继承了进程是CPU调度单位这一属性(也就意味着有自己的PC 寄存器 堆栈 状态),进程用于把资源集中在一起,而线程是CPU上被调度执行的实体
对于同一进程中的不同线程,它们共享进程的地址空间(存储器的抽象)等资源。线程间是没有保护的,因为共享硬件资源,无法用硬件实现保护。
每个进程中的内容 | 每个线程中的内容 |
---|---|
地址空间 | 程序计数器PC |
全局变量 | 寄存器 |
打开文件 | 堆栈 |
子进程 | 状态 |
即将发生的警告 | |
信号与信号处理程序 | |
账户信息 |
多线程在单CPU中运行时,其工作方式与多道程序设计的工作原理类似,也是CPU在线程间快速切换,制造了线程并行运行的假象,线程间切换也需要上下文切换
和进程一样,线程可以处于若干状态中的任何一个:运行、阻塞、就绪或终止
在多线程的情况下,进程通常会从当前的单个线程开始,该线程有能力通过调用一个库函数(thread_create)创建新的线程;当一个线程完成工作后,可以通过调用一个库函数(thread_exit)退出,然后该线程就消失了,不再可调度;在某些线程系统中,通过调用一个过程(thread_join,无返回值的函数),一个线程可以等待一个特定线程退出,这个过程阻塞线程调用,直至那个特定的线程退出;两个常见的线程调用是thread_yield,它让线程自动放弃CPU。
线程的实现方式
用户级线程
在用户空间建立线程库,提供一组管理线程的过程——运行时系统;运行时系统管理线程,具体来说是管理线程表,线程表和进程表类似(线程管理,不涉及资源),只不过它记录的是线程的PC、堆栈指针、寄存器和状态等。因为线程的所有信息都存放在进程中,所以线程间的切换不需要操作系统内核的介入,即不需要陷入内核,也因此操作系统内核管理的还是进程,它不知道线程的存在。
用户级线程的优缺点
优点
- 线程切换快,不需要内核的介入
- 允许每个进程有自己定制的调度算法
- 可以运行在任何操作系统上,只要实现了线程库
unix即采用这种方式实现线程
缺点
- 内核只将CPU分配给进程,同一进程中的线程只能有一个占用CPU并运行,除非第一个线程主动放弃CPU,若系统有多个CPU,同一进程的不同线程也不能运行在不同的CPU上,那么就不能很好的利用多CPU的优势
- 大多数系统调用是阻塞的,因此,由于内核阻塞进程,进程中的所有线程也会被阻塞
内核级线程
在内核空间建立线程库,内核管理线程并向应用程序提供API,线程表在内核中,所以线程的切换需要内核介入
内核级线程的优点:不同线程可以运行在多个CPU上,也不会因为系统调用的阻塞而导致其他线程的阻塞
缺点就很明显了,需要内核的介入,线程间切换慢;所有能够阻塞线程的调用都以系统调用实现,与运行时系统相比,代价较大
因为内核级线程的创建或撤销代价较大,某些系统会采取类似线程池的思路回收线程,以实现资源的复用
windows即采用这种方式实现线程
混合实现
结合用户级线程和内核级线程优点,线程的创建在用户空间中完成,但是线程的调度在内核空间中完成,每个内核级线程有一个可以轮流使用的用户级线程的集合。solaris即采用这种方式
参考资料: