更新于 

什么是线程

线程的概念和特点

线程的概念

考虑一个问题,如下图

很明显这是三个进程并发进行。那么在实际上的并发运行中操作系统会频繁的切换进程以达到同时以某种未知的速度进行(异步性的体现)。那么上节我们知道每次切换进程都要进行PCB更新来记录离开时的运行环境,毋庸置疑,这需要很大的时间开销。

并且从上图我们也可以看出进程是调度(即任务分配)和资源分配的基本单位,那么有没有一种而更好的模型可以减少时间开销的同时还能够保证功能的实现?

这时我们就引入了一个新的概念–线程,其实线程和进程很相似,如下图:

线程的优点

对比之前的模型,我们发现三个进程合并在了一个进程里,此时合并成为了一个QQ进程,而此时进程依然是资源分配的基本单位,他是分配资源的最下单位,但是此时三个合并的进程更名为线程,此时他们不再是资源分配的基本单位,三者共同共享QQ进程这一进程的共享资源,但是线程仍然是cpu调度的基本单位。此时进程成为了资源分配的基本单位,线程是调度的基本单位,这样,当切换线程时就没有必要频繁更新PCB的环境信息了也就减少了切换的时间开销。

对比两种模型,线程的引入只是减小了线程间并发的时间开销,而当时切换不同进程下的线程时,在时间开销并未得到优化,开销还是很大。当然此时,从属于不通进程的线程间的通信仍然必须请求操作系统的系统服务,而统一进程下的线程间可以直接在共享资源空间下进行读写的操作,所以此时同一个进程下的线程间通信不在需要操作系统的服务。

思考:引入线程的概念后进程发生了哪些变化?

引入线程前,进程同时是cpu调度和资源分配的基本单位,而当引入线程后,进程只是资源分配的基本单位,而线程成为了cpu调度的基本单位。所以可以理解为线程是一个寄存在进程下的小进程,小进程之间拥有直接通信,切换不用更新PCB,共用共享资源的特殊点而已。但是由于线程不是资源分配的单位,所以线程基本上不拥有独属于自己的资源空间,大部分都是共用的一个进程下的共享空间。当然在多cpu的环境下,各个线程也可以分配到不同的cpu上并行地执行且时间开销还很小。但是这里我们只讨论单核,所以线程之间也只会并发执行。

引入了线程的概念后,进程不仅仅是只能串行的执行某一个任务了,从宏观视角来看,此时cpu上的进程可以并发进行,同时某一个进程内的线程也在并发进行,所以并发性显著提高,此时进程只作为除cpu之外的系统资源的分配单元(如打印机,内存地址空间等都是分配给进程的),而线程则成为了处理机的分配单元。

线程的实现方式

用户级线程

用户级线程(User-Level Thread,ULT),用户级线程由应用程序通过线程库实现。所有的线程管理工作都由应用程序负责(包括线程切换),用户级线程中,线程切换在目态即可完成,无需操作系统的干预。在用户看来是有多个线程,但是在操作系统的视角来看,操作系统并意识不到线程存在(用户级线程对用户不透明,对操作系统透明),即用户级线程只是从用户的视角能够看到线程。

在早期的操作系统(如早期的UNIX),只支持进程,不支持线程。当时的“线程”是由线程库实现。以操作系统视角来看,根本就没有线程,而是就是三个进程。即在操作系统看来:

还是三个进程。。如果我们从代码的角度来看,线程其实就是一段代码逻辑,上述三段代码逻辑上可以看做是三个“线程”,而while循环就是“线程库”,线程库完成了对线程的管理工作(如调度,当然while循环就是通过If-else判断管理线程的)。

1
2
3
4
5
6
7
8
9
int main(){
int i=0;
while(true){
if(i==0){//处理视频聊天的代码}
if(i==1){//处理文字聊天的代码}
if(i==2){//处理文件传输的代码}
i=(i+1)%3;
}
}

很多变成语言提供了强大的线程库,可以实现线程的创建,销毁和调度等功能。因为用户级线程是由应用程序通过线程库实现的,所有的线程管理工作都是由应用程序负责(包括线程切换),所以在用户级线程中,线程切换在目态即可完成,无需操作系统的干预,当然进程切换还是得有操作系统完成的,不同进程下的线程间信息交流也需要操作系统的服务。由于用户级线程是只是在用户视角下有体现线程,但是在操作系统看来还是几个进程并发进行,所以一旦一个用户级线程被阻塞,整个进程就都被阻塞了,即其他的几个线程也会阻塞,并发度并不高。此时的多个线程不可在多核处理机上并行运行,因为只有在操作系统中的线程才可以在多核cpu上并行运行,而现在虽然有多个线程,但是在系统看来并没有意识到线程。

内核级线程

内核级线程(Kernel-Level Thread,KLT)又称“内核支持的线程”,内核级线程中用户所看到的线程都和操作系统中某一个线程对应(注意不一定是一一对应),所以此时的线程管理工作是由操作系统内核完成,线程调度、切换工作也都是由内核负责,所以也就不需要线程库了,因此内核级线程的切换需要在管态下才能完成。可以简单地理解为此时从操作系统的视角看内核可以看到线程。大多数的现代操作系统都实现了内核级线程,如windos,linux。

以上这些都属于内核级线程,一定要特别注意内核级线程和用户级线程的本质区别就是内核有没有内核级线程的概念,至于所说的多线程模型(下面会将)都是针对内核级线程而讨论的。并且一定要注意,因为操作系统只能看得到内核级线程,所以只有内核级线程才是处理机分配的单位。

操作系统为每一个内核级线程都建立了TCP(Thread Control Block,线程控制块,线程是Thread,进程是Process,所以进程控制块叫做PCB)来对内核级线程进行管理。优点是此时当一个线程在被阻塞后别的线程可以继续并发执行。且因为此时操作系统可以看到线程,所以此时的多线程可以在多核处理机上并行执行。缺点是一个用户进程会有许多的内核级线程,又因为此时的线程是由操作系统内核完成,所以需要频繁的变态,因为管理成本高,开销大。并且对比思考,PCB有不同的组织方式,那么TCB应该也有不同的组织方式。

思考:用户级线程和内核级线程的根本区别?

就是在操作系统内核看来能否意识到线程的存在即有无内核级线程的概念。

多线程模型

首先我们需要注意的是,当说到多线程模型时,操作系统首先是一定有了线程的概念,即此时肯定是可以意识到线程的存在的,所以用户级线程就没有多线程模型的以下几个分类的概念,即用户级线程不属于下面的任意一种。在支持内核级线程的系统中,根据用户级线程和内核级线程的映射关系,可以划分为以下几类。

一对一模型

一个用户级线程就对应与一个内核级线程,每个用户进程有与用户级线程同数量的内核级线程,优点是当一个线程被阻塞后,别的线程还可以继续执行,并发能力强,且此时多线程可在多核处理机上并行执行。缺点是一个用户进程就会占用多个内核级线程,线程切换由内核完成成本高。

多对一模型

优点是用户级线程的切换在目态下切换即可,线程管理的系统开销小,效率高,但是当一个用户级线程阻塞时,整个进程都会被阻塞,并发度不高。多个线程不可以在多核处理器上并行运行。(和用户级线程很想,但不是用户级线程的模型,因为此时系统能够意识到线程即内核级线程的存在)。

多对多模型

n个用户线程映射到m个(m<=n)内核级线程,每个用户进程对应着m个内核线程。克服了多对一模型的并发度不高的缺点的同时又克服了一对一模型中开销太大的缺点,所以性能较为稳定不极端。还记得前面强调的内核级线程才是cpu调度的基本单位吗,所以此时这个用户进程虽然有3个用户级线程,但是一次性只能个有两个内核级线程获得cpu的调度。

因此我们可以理解为用户级线程是“代码逻辑”的载体,而内核级进程是“运行机会"的载体,内核级线程才是处理机分配的单位,例如:多核cpu环境下,上面这个进程最多被分配到两个和并行执行。一段”代码逻辑“只有获得了“运行机会”才能被cpu执行。内核级线程可以运行任意一个有映射关系的用户级线程的代码,所以只有两个内核级线程中逻辑都被阻塞时,这个进程在会被阻塞。

总结