在上个小节中 我们学习了线程相关的一些基本概念 基础的知识
那这个小节中 我们回来看一下有哪几种线程的实现方式 并且会学习几种多线程模型
那线程的实现方式分为用户及线程和内核及线程 另外 还有的系统当中会把这两种实现方式都混合起来使用
那这个大家一会会看到实际的例子 那首先我们来看一下 第一种线程的实现方式叫做用户级线程
其实这种实现方式是在早期的操作系统 也就是只支持进程 还暂时没有支持线程的那些操作系统当中
来使用的 当时所谓的线程是由程序员们写的线程库来实现的
也就是说 在这个时代 操作系统的视角看到的其实依然是只有进程
但是程序员们写的这些应用程序当中 可以使用线程库来实现多个线程并发的运行这样的事情
我们还是来结合上小节当中提到的这个例子来进行理解 上个小节中我们提到过
我们的qq可以一边视频聊天一边文字聊天 一边实现文件传输 那上个小节中我们提出了这样的方案
如果要让这三个事情并发的运行的话 那么在不支持线程的系统当中 我们可以分别建立三个进程 这三个进程分别是处理其中的某一个事情
进程一的代码是不断不断的来处理视频聊天这个事情 进程二是不断不断的来处理这个文字聊天 而进程三是处理这个文件传输
那我们可以看到 处理视频聊天的这个代码是在是用这个循环来一遍一遍一遍的不断的执行的
另外的两个代码也一样 所以其实我们可以用这样的方式来实现 让这三段代码并发的运行
我们用一个循环 让它一直不断的循环 然后这个 i 的值会012012这样循环的变化
当 i 等于零的时候 我们可以让这个进程来处理视频聊天
当 i 等于一的时候 让他处理文字聊天 当 i 等于二的时候让他处理文件传输
那由于我们这个程序 进行y循环的速度是非常快的 那我们其实也可以把这三段代码
看作是三段并发的运行的代码 他们分别处理了不同的事情 所以其实如果我们从单纯代码的角度来看的话
那么一个线程 其实我们可以把它理解为是一段代码逻辑 那这个地方提到的这三段代码逻辑 我们就可以把它看作是三个线程 另外 我们这儿写的循环的这个处理逻辑 其实我们就可以把它看成一个最简陋 最弱智的一个线程库
这个线程库完成了对各个线程的一个管理调度的工作 那这个落那 这个弱质线程库对我们的这些线程的调度规则很简单
就是第一次先处理视频 第二次处理文字 第三次处理文件 第四次又处理视频 第五次处理文字等等等等
这个地方 我们用一个简单的while循环和几个if语句就实现了一个最简单最简单最简单的线程库
不过他们提供的线程库要比我们的外循环复杂多了 程序员可以利用这个线程库来实现这个用户及线程的创建 销毁 调度等等一系列的功能
那接下来 问题来了 在刚才我们所说的这个例子当中 操作系统其实他只看得到进程
而这个进程上处理机运行的时候 其实是程序员自己写了一个线程库来创建了逻辑上的线程
也就是这所谓的用户及线程 那我们来思考这样的几个问题 第一这些用户及线程的管理工作是由谁来完成的
其实我们刚才写的这个while循环就是简单的实现了对这三个线程的管理 让他们交替的运行
所以 用户级线程的管理工作是由应用程序通过线程库来完成的
并不是操作系统负责的第二个问题 线程切换是否需要cpu
从用户态转换为内核态 那经过刚才的分析 这个问题其实也不难回答
我们的线程切换其实是由我们这个循环来控制的 这并不需要涉及到请求操作系统服务之类的事情
所以 线程切换的管理是由我们的线程库应用程序自己完成的
在用户态下就可以完成线程的切换工作 并不需要操作系统的干涉
第三个问题 操作系统是否能意识到用户级线程的存在 那显然操作系统他只能看到这个进程的存在
他只知道这个进程 它是一坨代码 而在这坨代码里面又分别被分为了几个线程 操作系统是意识不到这些线程的存在了
所以 这也是为什么这种线程的实现方式叫做用户及线程的原因
只有用户才能感知到这个用户及现成的存在 而操作系统其实感知不到这些用户及现成的存在
那最后我们要思考的问题是 这种实现方式 它有什么优点和缺点呢
首先来看优点 刚才我们提到过用户级线程的管理工作 包括切换 创建等等 这些工作都不需要请求操作系统的服务 只需要在用户态下就可以完成
也就是说 对用户级线程的管理并不需要涉及到cpu变态这个事情
而之前我们说过cpu变态是有开销 有成本的 所以那既然用户及线程的管理工作不需要cpu切换到内核态 所以对于他们的管理工作 肯定开销是比较小 效率是比较高的
那接下来来看一下用户级线程 这种实现方式有什么缺点 那我们回到我们自己实现的这三个最简单的用户级线程
我们来看一下 假设此时这个qq进程上处理机运行 而这次运行的时候
i 的值是等于零的 也就说视频聊天的这一段代码会上处理机运行
但是 假设视频聊天的这段代码在运行的过程中发生了阻塞
比如说 他想要申请摄像头那个资源 但是申请失败 那么由于他想要的这个系统资源得不到满足 因此这段代码的执行就会被阻塞
那我们想一下 既然这个代码的执行被阻塞在了这个地方 那么这个while循环还能继续下去吗
肯定不行了 对吧 只有这个阻塞被解除之后 这个循环才可以继续执行下去
所以 这种用户级线程的实现方式有一个很明显的缺点 那就是
如果其中的某一个线程被阻塞 那么其他的这些线程也会被阻塞 也没办法执行下去
那从这段伪代码当中 相信不难理解这一点 所以这就是用户级线程这种实现方式最大的一个缺点
只要其中一个被阻塞 那整个进程都会被阻塞 所以这种方式的并发度并不高
另外 虽然上个小节中我们提到过 引入线程之后 线程成为了cpu调度的基本单位
但是 如果这个线程是用用户籍线程这样的方式来实现的话 那么在这种情况下 其实cpu的调度单位依然是进程
操作系统是给进程分配cpu时间的 因此即便我们的电脑是多核处理机
但是 由于进程才是cpu调度的基本单位 因此这个进程只能被分配一个核心
所以 这些线程并不能并行的运行 那这是早期的操作系统当中人们实现线程的方式
在这个阶段 操作系统还只支持进程 并不支持线程 那之后随着操作系统的发展
越来越多操作系统开始支持线程 那操作系统支持的这种线程就叫做内核级线程
那这种内核及线程就是操作系统视角也可以看得到了线程 那现代的操作系统大多都支持内核级线程 比如说我们很熟悉的windows linux等等
那接下来我们还是要思考同样的三个问题 在引入了内核及线程之后 这个线程的管理工作到底是谁来做呢
那由于这个内核及线程是在操作系统层面实现的线程 因此这个内核级线程的管理工作当然是需要由操作系统来完成
第二个问题 线程的切换是否需要cpu状态的转换 那既然这些内核及线程由操作系统负责管理
那他们的切换 他们的管理工作肯定是需要操作系统介入的 因此在进行线程切换的时候 当然是需要从用户态转变为内核态
第三个问题 操作系统是否能够意识到内核级线程的存在 这个不用说了 最后我们根据刚才认知道的这些信息来分析一下
这种实现方式有什么优点和缺点 首先来看优点 如果某一个操作系统 它支持内核及线程的话
那么 在这种操作系统当中 内核级线程 它是处理机调度的基本单位
而进程只作为分配资源的基本单位 因此在多核cpu的环境下
这几个线程可以分别分配到不同的核心下并行的执行 另外呢 不同的内核及线程中可以跑不同的代码逻辑
比如说这个代码逻辑是实现视频聊天 这个是实现文字聊天 这个是实现文件传输 那么 由于内核及线程 它是处理及分配的基本单位 那在这种情况下 即便其中的某一个线程被阻塞 那其他的两个线程依然可以继续执行下去
所以采用这种方式有一个优点 那就是线程之间的并发能力强 那再来看一下这种方式的缺点
当引入了内核级线程之后 那一个进程有可能会对应多个内核级线程
那操作系统需要对这些线程进行管理 所以内核及线程之间的切换是需要cpu从用户台变为内核态的
当切换完成之后 还需要从内核态转回用户态 而之前我们提到过很多次cpu变态 是有成本有开销的
所以这种实现方式会导致现成的管理成本要更高 开销更大 那刚才我们学习了用户级线程和内核级线程这两种线程的实现方式
那这两种方式都有各自的优点和缺点 那有没有可能把这两种方式结合起来呢
那在支持内核级线程的系统当中 如果在引入线程库的话 那么我们就可以实现把若干个用户及线程应设到某一个内核级线程这样的事情
那根据用户级线程和内核级线程的这种映射关系 就引出了三种多线程模型
像刚才我们一直在讲的这种模型 一个用户及线程对应一个内核及线程 这个是一对一模型
那如果采用这种映射方式的话 一个进程 它有多少个用户级线程 就会有多少个内核级线程 它们都是一对应的
那这种方式的优点呢 就是刚才我们提到过的 一个线程被阻塞之后 别的线程还可以继续执行 因为内核级线程是处理及分配的基本单位
另外 这些代码逻辑 这些线程可以分配到多核处理机上并行的执行 这是它的优点
而缺点呢 和刚才我们所说的一样 就是管理的成本高 开销大 因为线程的管理工作肯定需要切换到内核态
那只要涉及到cpu变态 就会使开销变大 那再来看
第二种多线程模型 叫做多对一
也就是多个用户级线程映射到一个内核级线程 那如果是这种映射关系的话 其实它就退化成了我们之前提到的
纯粹的用户级线程那种实现方式 那由于一个进程只被分配到了一个内核机的线程
而在这个内核级线程上面 通过线程库又实现了三个用户级的线程 因此这些线程的管理工作只需要在用户态下就可以完成
所以线程的管理开销小 效率高 但是缺点呢就是其中的一个用户级线程阻塞之后 会导致其他的用户级线程也跟着被阻塞 并发性不高
并且 这些用户级线程是不可能并行的运行的 因为只有内核级线程才是处理机的分配单位 如果一个进程 它只对应一个内核级线程的话
那么 在同一时刻 这个进程肯定只能被分配一个cpu的核心 当然如果给这个进程分配多个内核级线程的话
那么在多核cpu环境下 这些内核级线程肯定是可以并行的运行的 不过在我们的考试当中
如果提到这种多对一的线程模型的话 那么我们默认一个进程只被分配了一个内科技的线程
那最后 我们要认识的是多对多模型 就是把n个用户级线程映射到m个内核级线程上
n的数量要大于等于m 那在这个模型当中 由于一个进程 它有两个内核级线程
因此 其中一个内核级线程被阻塞的话 另一个内核级线程是可以继续运行下去的 因此它克服了多对一模型并发度不高的缺点
另方面 这种多对多的模型n是大于等于m的 也就是说内核级线程的数量它要比用户级线程的数量要更小
因此 操作系统对这些线程的管理开销也相应的会更小 而在一对一模型当中 有多少个用户极限程就需要给他创建多少个对应的内核级线程
那内核级线程太多的话 操作系统的管理开销就会更大 所以这种方式他又克服了一对一模型当中
线程管理开销太大的一个缺点 那我们再来总结一下用户级线程和内核级线程的
一个区别和联系 我们可以这么难理解所谓用户级线程
我们可以把它理解为是一个代码逻辑的载体 比如说这个用户及现成
它承载的是文字聊天相关的代码逻辑 这个用户及现成它承载的是文件传输相关的代码逻辑
而内核级线程可以理解为是运行机会的一个载体 因为操作系统在分配处理机cpu资源的时候
是以内核级线程为单位进行分配的 所以在这边这个模型当中虽然有三个用户级线程
但是啊 这个进程最多只可能被分配两个cpu的核心
我们的一段代码逻辑只有获得了运行机会的时候 他才可以被cpu执行
那这可以让我们的线程管理有更多的灵活性 比如说在这边这个例子当中 如果我们的qq视频聊天需要耗费比较多的cpu资源的话
那么我们可以让左边这个内核级线程 让他专门来执行视频聊天相关的
这个代码逻辑 而右边这个内核级线程 我们可以让它并发的执行文件传输和文字聊天这两个部分的逻辑
那如果某一个时刻文件传输又要耗费很多的cpu资源的话 那么我们可以把文字聊天这块的逻辑把它映射到这边 让这个内核级线程来进行处理
那需要注意的是 在引入了内核级线程之后 一个进程可能会对应多个内核级线程
而只有所有的这些内核机线程都被阻塞的时候 我们才说这个进程进入了阻塞状态
那需要再次强调的是 用户级线程是在用户视角能看到的 线程由线程库实现
就像那个很简单粗暴的循环 可以认为是一个最简单的线程库 那内核级线程才是操作系统视角能看到到的线程由操作系统
来负责管理 所以内核及现成才是处理及分配的单位 那对于我们介绍这几种多现成模型来说 大家主要是要理解他们各自的优缺点
另外 这个部分的内容比较容易考察 关于阻塞的问题 这个大家在课后习题当中会有体会
好的 那么以上就是这个小节的全部内容
推荐一个零声学院免费公开课程,个人觉得老师讲得不错,分享给大家:Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,立即学习