1.引入线程的背景
我们知道进程的两个基本属性:
进程是一个拥有资源的独立单元;
进程同时又是一个可以被处理器独立调度和分配的单元。
总的来说,为了使多个程序更好地并发执行,并尽量减少操作系统的开销,操作系统设计者引入了线程,让线程去完成第二个基本属性的任务,而进程只完成第一个基本属性的任务。
事实上,进程控制块就是为了让各个进程之间能够给很好的隔离。但是有的场景下,多进程的隔离性设计不满足实际需求,举个例子:
我们现在有一个mp3播放程序,它可以分为三部分:
while(true)
{
Read();//IO读数据
Decompress();//CPU解压数据
Play();//播放
}
如果这样写的话,IO和CPU不断的切换,资源使用率低下,而且播放出来的声音可能不连贯。(上一波数据缓冲已经播放完,下一波的数据还没有到达)那么改成多进程呢?
//程序一
while(true)
{
Read();//IO读数据
}
//程序二
while(true)
{
Decompress();
}
//程序三
while(true)
{
Play();
}
可是问题是这三个进程怎么通信,共享数据呢? 两个进程之间的通信要通过系统调用从内核中绕一圈,如果都在一个进程中就好了。
另外多进程也会加大系统开销:创建进程、销毁进程、切换进程等。
所以线程就应运而生了。
线程是进程内部的一类实体,满足以下两个特性;
1)实体之间可以并发执行
2)实体之间共享相同的内存地址空间
有了线程之后,我们把相关的执行流的信息变成线程控制块,它所为进程控制块的新的一部分。这样,在线程控制块中就可以有多个指令指针、多个堆栈和多个CPU寄存器的现场保护,这些都和执行流相关。如下图:
线程 = 进程 - 资源共享
多进程的缺点:
- 创建进程的过程开销大
- 为了完成进程间的数据交换,需要特殊的IPC技术
所以为了保持多进程的优点,并在一定程度上客服其缺点,引入线程。它的优点是:
- 线程创建和上下文切换比进程更快
- 线程间数据交换无需特殊技术
线程的定义
线程是进程内一个相对独立的、可调度的执行单元。线程自己基本上不拥有资源,只拥有一点在运行时必不可少的资源(如程序计数器、一组寄存器和栈),但它可以与同属一个进程的其他线程共享进程拥有的全部资源。多线程是指一个进程中有多个线程,这些线程共享该进程资源。但是各线程自己堆栈数据不对其他线程共享。
2.进程和线程的关系
- 进程是操作系统进行资源分配的基本单位
- 线程是调度的基本单位
进程中的所有线程共享该进程的状态和资源,进程和线程的关系如下图:
例如QQ 和浏览器是两个进程,浏览器进程里面有很多线程,例如 HTTP 请求线程、事件响应线程、渲染线程等等,线程的并发执行使得在浏览器中点击一个新链接从而发起 HTTP 请求时,浏览器还可以响应用户的其它事件。
二者具体的区别:
Ⅰ 拥有资源
进程是资源分配的基本单位,但是线程只有和指令执行流相关的必要资源,如寄存器和栈等,线程可以访问隶属进程的资源。
Ⅱ 调度
线程是独立调度的基本单位,在同一进程中,线程的切换不会引起进程切换,从一个进程中的线程切换到另一个进程中的线程时,才会引起进程切换。
Ⅲ 系统开销
由于创建或撤销进程时,系统都要为之分配或回收资源,如内存空间、I/O 设备等,所付出的开销远大于创建或撤销线程时的开销。类似地,在进行进程切换时,涉及当前执行进程 CPU 环境的保存及新调度进程 CPU 环境的设置,而线程切换时只需保存和设置少量寄存器内容,开销很小。
Ⅳ 通信方面
线程间可以通过直接读写同一进程中的数据进行通信,但是进程通信需要借助 IPC。
二者的侧重点
进程为了使同一台计算机上可以运行多道不相关联的任务,而线程的引入主要目的在于提高同一个任务完成的效率。
3.看看本质
我们知道,每个内存有自己独立的内存空间,包括保存全局变量的数据区,供动态分配使用的堆区,函数运行时使用的栈区。
但如果以获得多个代码执行流为主要目的,则不需要向上面那样完全分离内存结构,只需要分离栈区域就可以。这样可以获得如下优势:
- 上下文切换不需要切换数据区和堆;
- 可以利用数据区和堆交换数据(通信);
实际上这就是线程,线程为了保持多条代码执行流而隔离了栈区域,它的内存结构如下:
为了保持上图中,多个线程共享数据区和堆的结构,线程将在进程中创建并运行。即,进程和线程可以定义为如下形式:
- 进程:在OS中构成单位执行流的单位;
- 线程:在进程中构成单独执行流的单位
如果说在OS中生成了多个执行流即多进程的话,那么在同一进程内部创建多个执行流就是多线程了。OS、进程和线程的关系可以表示成下图:
4.线程的生命周期
线程的一些状态:
1、新建:创建线程对象
2、就绪:线程有执行资格,没有执行权
3、运行:有执行资格,有执行权
4、阻塞:由于一些操作让线程改变了状态,没有执行资格,没有执行权
另一些操作可以把它给激活,激活处于就绪状态
5、死亡:线程对象变成垃圾,等待被回收
5.线程的分类
线程可以分为用户级线程、内核线程。
5.1用户级线程
是用户通过自己写的线程库来完成线程的管理,包括线程的创建、终止、调度等。操作系统并不会感知用户态中有多线程的支持。
优点:
- 不依赖于操作系统内核;
- 同一进程内的用户线程切换速度快;
- 允许每个进程有自己的线程调度算法;
缺点:
- 线程发起系统调用而阻塞时,则整个进程将进入等待;
- 不支持基于线程的处理机抢占:除非当前运行线程主动放弃,它所在的线程无法抢占CPU;
- 操作系统按进程分配CPU时间片,所以多个线程中,每个线程的时间片较少;
5.2 内核级线程
是由内核通过系统调用方式实现的线程机制,由内核完成线程的创建,终止和管理。
特点:
- 将由内核完成PCB和TCB;
- 线程执行系统调用而被阻塞不影响其他线程;
- 线程的创建、终止和切换开销相对较大
- 可以以线程为单位进行CPU时间分配