215_线程概念和多线程模型
# 2.1_5_线程概念和多线程模型
各位同学大家好,在小节中我们会学习线程相关的一系列的知识点。
首先我们会用一个例子来介绍什么是线程,为什么要引入线程机制。
在引入线程机制之后,比起传统的进程机制来说带来了哪些变化?
之后我们会介绍线程有哪些重要的属性,并且会介绍线程的几种实现方式,主要是用户级线程和内核级线程。
最后我们会介绍多线程的几种模型,多对一模型、一对一模型和多对多模型,我们会按一从上至下的顺序依次进行讲解。
# 什么是线程
首先来看一下什么是线程,为什么要引入线程
在很久很久以前,在没有引入进程之前,系统之间的各个程序是只能串行执行的,所以在那个时候,比如说像我们想一边运行音乐播放程序,一边运行 QQ 这个程序,那么显然是不可以实现的。在那个时候我们不可能边聊 QQ,然后边听音乐,
但是之后引入了进程的机制之后,就可以实现 QQ 边聊 QQ 边听音乐这样的事情,但是大家再来深入的思考一下,QQ 可以做一些什么事情,我们可以用 QQ 进行视频聊天,同时还可以和其他的人进行文字聊天,然后再同时还在传送文件。那么这些事情是怎么在进程当中完成的呢?很显然在传统的进程定义当中,进程它是程序的一次执行,但是这些功能视频聊天、文字聊天传送文件,这些功能显然是不可能由一个程序顺序执行来处理的,如果只能顺序的来处理,那么在我们用户看来这几件事情是可以同时发生的,就不可能有这样的现象。
所以有的进程其实它是需要同时处理很多事情的,就像刚刚才咱们说的 QQ 那样,但是传统的进程它只能串行的执行一系列的程序代码,就是这个样子。在传统的进程机制当中,CPU 会轮流的为各个进程进行服务,那么这些进程就可以并发的执行,并且每一个进程会有他自己相应的一系列程序代码,然后被 CPU 服务的时候,这些代码就可以一句一句开始往下执行,所以在传统的进程机制当中,进程是执行流的最小单位。这句话什么意思?听了后面的讲解,大家应该就可以理解。
之后为了满足像咱刚才咱们说的一个进程当中,同时就宏观上同时做很多事情,人们又引入了线程机制,用来增加系统的并发度,引入了线程之后,系统的 Cpu 的调度服务对象就不再是进程,而是进程当中的线程。每一个进程当中可能会包含多个线程,然后 CPU 会轮流的被用一定的算法轮流的为这些线程进行服务,就像这个样子为各个线程服务,那么这样的话同一个进程当中被分为了多个线程,像刚才咱们说的 QQ 视频聊天和传送文件,这两件事情如果想要并发的执行的话,那么我们就可以把这两件事情对应的处理程序放到两个不同的线程下,那么这两个线程可以并发的执行,自然这两件事就可以并发的完成。
所以在引入了线程机制之后,线程就成了程序执行流的最小单位。在没有引入线程之前,一个进程其实就对应一份代码,这些代码只能顺序的依次往下执行,但是在引入了线程之后,每一个进程可以有多个线程,并且这些线程它可以有各自不同的代码,也可以是不同的线程,运行的是同一份代码,但是这些代码都会并发的被 CPU 处理,然后并发的依次执行下去,所以这就是所谓程序执行留的最小单位的意思。
所谓的线程其实我们可以把它理解为是一种轻量级的进程,以前 CPU 调度的单位是进程,但是现在 CPU 的服务对象不是进程,而是以线程为单位,所以线程它是基本的 CPU 执行单元,也是程序执行流的最小单位,经过刚才的讲解,相信大家已经可以理解,在引入了线程之后,进程之间可以并发的执行,并且进程之间的各个线程也可以并发的执行,所以引入了线程机制,进一步的提高了系统的并发度,可以使得一个进程内也可以并发的处理各种各样的任务。就像刚才咱们聊到的 QQ 聊天什么传文件这样这些事情,然后在引入了线程之后,进程不再是 CPU 调度的基本单位进程,它只作为除了 CPU 之外的系统资源的分配单元,什么意思呢?假如系统当中假如说计算机系统当中有各种各样的系统资源,那么这些资源是被分配给进程的,而不是分配给线程,就像这个样子,
# 引入线程后的优点
那么在引入了线程机制之后,比起传统的进程机制来说有了哪些变化?首先我们来看一下资源分配和处理机调度方向。刚才已经讲过传统的进程机制当中进程它既是资源分配的基本单位,也是处理机调度的基本单位。但是在引入线程之后,进程它只是资源分配的基本单位,而线程变成了调度的基本单位,这是区别。
在并发性角度来讲,传统的进程机制中进只能是进程之间并发的执行,但是在引入了线程之后,各个线程间也可以并发执行,所以进一步提升了系统的并发度。
在实现并发带来的系统开销方面,传统的进程间并发需要切换进程的运行环境,切换进程的运行环境其实系统开销是比较大的,但是引入了线程机制之后,如果我们是切换同一个进程内的不同线程,那么我们不需要切换进程的运行环境,这样的话并发所带来的系统开销就会进一步的降低。
怎么理解切换进程的运行环境所带来的系统开销,我们来用一个去图书馆看书的例子来理解。假如说你在使用图书馆当中的某一张桌子,但是突然有一个人你不认识的人也要用这个桌子,那么你需要把你的运行环境,你的书给收走,然后他要把自己的运行环境把他自己的书放到桌子上,所以这就是进程切换所带来的运行环境的切换。这个切换过程其实需要付出一定代价的,你需要把书搬走,他需要把书放上去,但是如果说这个时候是你的舍友想要用这张书桌的话,那么既然你们认识,相当于你们俩是属于同一个进程的,这种情况下就可以不把你自己的桌子不把你自己的书收走,把这个书依然放在桌子上,这就类似于同一进程内的线程的切换,同一进程内的线程切换不需要切换进程的运行环境,所以由于你们不需要把书挪来挪去,因此这个开销也会降低很多。
# 线程的属性
接下来我们再来看一下线程有哪些属性。线程是处理机调度的单位刚才已经强调过很多次了,然后多核 CPU 的计算机当中每个线程它可以占用不同的 CPU,比如说现在的 CPU 一般都是什么双核四核八核的,各个线程可以分配到不同的核心里去。
另外每个线程它其实像会有一个线程 ID 和线程控制块 tcb 这样的数据结构,线程控制块其实有点类似于我们之前学过的 PCB 进程控制块,线程控制块也是用于管理线程所创建的一个数据结构,那么和进程类似,线程它也会有就就绪,阻塞,运行这样的三种基本状态。
因为引入了线程机制之后,线程它只是处理机调度的基本单位,而系统资源分配是分配给进程的,所以线程几乎不拥有系统资源,系统资源都是在进程那里,那么线程肯定也需要使用一系列的系统资源,这些系统资源从哪里来呢?其实同一个进程当中的不同线程,他们是可以共享使用进程所拥有的系统资源的。这些系统资源,包括像什么 lO 设备,还有内存地址空间这样的资源。由于同一个进程当中的不同线程,他们可以共享内存地址空间,所以同一个进程中的线程,它们之间的通信就可以不需要操作系统干预,可以直接通过共享的内存地址空间就可以完成它们之间的信息传递。
另外我们还需要注意,以同一个进程中的线程切换其实并不会引起进程切换,但是不同的进程中的线程切换会引起进程切换。如果我们切换的是同进程内的线程,那么由于不需要切换进程的运行环境,所以系统开销是比较小的。如果我们切换的是不同进程间的线程,那么它会导致进程的切换,相应的也需要切换进程的运行环境,所以系统开销就比较大,刚才咱们讲到的图书馆那个例子。那么这就是线程的相应的一系列属性,经过刚才的讲解,相信大家对线程已经有了比较直观的理解。
# 线程的实现方式
下面我们来看一下线程有哪些实现方式,线程的实现方式分为两种,一种叫做用户级线程,一种叫做内核级线程。
首先来看用户级线程,在用户级线程当中,这些用户级线程它是应用程序通过线程库来实现的,所有的线程管理工作,包括像线程切换这样的工作都是由应用程序来负责,所以相应的这些线程切换肯定也不需要切换到核心态,他们在用户态下就可以完成,而不需要操作系统的干预。
在这种实现方式当中,在用户看来,从用户的视角看,进程它是有多个线程的,但是操作系统并看不到这些线程的存在,所以说用户级线程它对用户是不透明的,用户是可以看到的。但是对操作系统来说,用户级线程是透明的,操作系统只能看到这个用户进程,调度依然是以用户进程为单位,所以我们可以这样理解,用户级线程其实就是从用户的视角可以看到的线程。
第二种线程的实现方式叫做内核级线程,又可以称之为内核支持的线程。在内核级线程当中,线程的管理其实是由操作系统内核来完成。所以线程调度,线程切换这些线程的管理工作,当然也需要由操作系统内核来负责,所以内核级线程的切换肯定是需要在核心态下才可以完成。
和刚才类似,我们可以把内核级线程看作是从操作系统角度,操作系统内核的视角来看,能看到的线程。所以在这个模型当中,其实对于用户来说进程拥有三个线程,对于操作系统来说,进程同样是拥有三个线程,
在有的操作系统当中只支持用户级线程,有的只支持内核级线程,但是也有的操作系统是同时支持上面两种线程的实现方式的,所以可以采用二者组合的方式,把 n 个 m 用户级线程映射到 m 个内核级线程上。
那么大家需要注意的一点是,由于内核级线程是操作系统能够看得见的线程,所以操作系统在为这些线程分配处理及资源的时候,它肯定是以内核及线程为单位来进行分配的,所以内核级线程才是处理及分配的单位,这一点重要。
举个例子,像这边这个模型当中用户级三个用户级线程映射到了两个内核级线程当中,但是由于内核级线程才是处理机分配的单位,所以即使我们把进程放到一个四核处理机的计算机上运行,由于它只有两个内核级线程,那么进程最多只能被分配到两个核心,所以最多只会有两个线程来并行的执行,而不是三个线程并行的执行。理解这一点,对理解整个内核用户级线程相应相关的知识点都十分重要。
# 多线程模型
那么在同时支持用户级线程内核级线程的这些系统当中,几个用户级线程映射到几个内核级线程,由这个问题引出了所谓的多线程模型的问题。
那么多线程模型分为三种,这是第一种,就是多对一模型,也就是把多个用户级线程映射到了一个内核级线程上,每一个用户进程它只会对应一个内核级线程,这是多对一模型的特点。
那么多对一模型有这样的优点,用户级线程由于它的管理是由应用程序来负责的,所以用户级线程之间的切换只需要在用户空间用户态下就可以完成了,由应用程序负责完成。它的切换不需要切换到核心态,所以线程的管理开销会比较小,效率会很高,
但是它的缺点也很明显,由于内核级线程才是处理机分配的单位,所以如果一个用户级线程,它如果当前内核及线程正在处理的是某一个用户级线程的逻辑的话,那么如果用户级线程发生了阻塞,那么就会导致内核及线程相应的也被阻塞。由于整个进程只会对应一个内核级线程,所以这个内核级线程阻塞了,也会导致整个进程都发生阻塞,那么其他的这些用户及线程就不可以继续执行下去了,所以多对一模型的缺点就是并发度不高,并且多个用户级线程不可以在多核处理机上并行的运行,因为它们只对应一个内核级线程。
第二种多线程模型是一对一的模型,其实一对一的模型就是变成了纯粹的内核级线程,那么在一对一模型当中,一个用户级线程会对应一个内核级线程,这种模型的优点就是如果某一个用户级线程被阻塞了,那么其他的这些用户级线程还可以继续执行下去,所以它的并发能力是很强的。并且多个用户级线程由于都各自映射映射到了不同的内核级线程上,而内核级线程又是处理机调度分配的基本单位,所以这些用户级线程是可以在多核处理机下并行的执行的。
但是它所带来的缺点就是一个用户级线程会一个用户进程,它可能会占用多个内核级线程,并且线程的切换是由操作系统内核来完成的,所以切线程切换需要切换到核心态,线程的管理成本相比于多对一模型来说要更高,系统开销要更大。
那么为了解决这些问题,又提出了多对多的模型,也就是把 n 个用户级线程映射到 m 个内核级线程上,这也是我们刚才说的组合模式当中用到的模型。比如说像这个图当中就是把三个用户级线程映射到了两个内核级线程当中,多对多模型的优点是它克服了多对应模型并发度不高的缺点,并且还克服了一对一模型当中一个用户进程占用太多内核线程切换开销太大的缺点,所以对多模型可以说是集二者之所长,它是一种更合理的解决方式。
# 小结
那么我们再来回顾一下小节的内容,小节的这些知识点很容易作为选择题做来考察。
刚开始我们介绍了线程相关的一系列的概念,并且介绍了引入线程机制之后,和传统的进程机制相比都有哪些变化。
之后我们介绍了线程的一些重要的属性,这我只列出了其中几个比较容易考察的属性,特别是线程式处理机的调度单位进程是资源分配的单位经常在考选择题当中出现,那么其他的那些属性大家只需要理解,不需要死记硬背,
之后我们又介绍了线程的三种实现方式,主要是用户级线程和内核级线程,并且还有二者的组合方式,其中内核级线程才是处理机分配的单位,这一点对于理解这一系列的知识点都十分重要。
那么最后我们又介绍了几种多线程模型,多对一对一和多对多,我们分别分析了各种模型的优点和缺点,其中对于多对一模型来说,一个线程的阻塞会导致整个进程都被阻塞,因为内核级线程才是处理机的分配单位,那么线程的实现方式和多线程模型这两块是比较难理解的一个难点,但是又很容易在选择题当中进行考察,同学们可以再结合课本的课后习题再进行进一步的巩固和理解。
好的,那么这就是小节的全部内容。
- 01
- 中国网络防火长城简史 转载10-12
- 03
- 公告:博客近期 RSS 相关问题10-02