参考资料
sc2.1_6_线程的实现方式和多线程模型_哔哩哔哩_bilibili
一、线程是啥
在前后端开发里我们就经常遇到线程,现在操作系统也遇到了,但是线程到底是什么?这里就先解释一下
1、理论理念
所谓线程,简单理解就是一个进程里【执行代码语句的顺序】
专业的说法是:线程是进程里最基本的执行单位 ,是CPU调度的最小单位
通俗可以理解成一条道路,执行一条代码就是通过一辆汽车,单线程就是只有一条路,多线程就是有多条路;
那么如果只有一条路,是不是只能一辆车跟着一辆车按顺序通过?这就是单线程
;
那么如果单线程里前面有一辆车故障堵住了,后面的车不就得干等?如果是多条路,那么有些车就可以到别的路上,不用在原来一条路等前面的车,这就是多线程
2、代码理解
我将用Python和JAVA两种语言的示例代码进行解释:
【Python】的多线程
先看单进程情况,单进程下,主函数依次一条一条代码从上往下按顺序执行
;
用【threading.Thread( )】函数之后可以创建一个多线程,并指定这个线程执行什么事情,通过 "线程.start()" 函数让其启动
那么这里创建了第二个线程 "t1",并指定执行worker这个函数,于是可以看到主函数没有去等worker函数的逻辑执行完直接就执行到底了,然后t1线程还在执行worker剩下的内容任务
这是因为相当于开辟了两条 "道路",主函数单独走自己的路,t1进程也单独走自己的路,互不影响,因此主函数直接走到底了,然后t1也同时走自己的路一路到底,不存在什么先后顺序了
;
下面这个例子直接创建"t1"、 "t2"两个线程,现在主函数、t1、t2相当于3条路,各自都同时往后执行,互不影响,所以能看到有时t1先输出结果、有时t2先输出结果,但是没有具体谁先谁后,因为他两同时进行的
【JAVA】示例多线程
单线程情况下,从上到下按顺序执行每一行代码
;
那么用【new Thread()】可以创建一个线程对象,在这个对象里可以写你要执行的内容,然后再用 "线程.start()" 的方法就可以启动这个线程
那么可以看到多线程的情况,每一次执行,输出的结果都不一样,是因为这三个进程是同时进行的,并没有明确的先后顺序之分
3、实际应用理解
但是计数看了这么多例子,可能还是有人不理解究竟为什么说线程是:进程里最基本的执行单位 ,是CPU调度的最小单位
首先,传统的计算机里,系统里的每一个程序都只能串行运行,在运行QQ时想运行音乐软件,就得停掉QQ再运行音乐程序;但是有了【进程】之后,就可以边听歌边用QQ了
但是对于一个进程里,比如QQ这个例子,我们平时可以用QQ同时看视频、文字聊天、传送文件......,那么这些事明显不是在这个进程里按顺序执行的
传统的进程内,就是CPU分时地对各个进程进行按顺序的串行执行每一行代码
因此为了处理 "高并发运行 ",现在的进程里都引入了【线程】,CPU不再是只执行进程,而是通过特定的算法分别执行各个线程
可以把【线程】理解为 "轻量级的进程",他才是CPU最基本的执行单元 、CPU调度的最小单位 、程序执行流的最小单位 、进程里最基本的执行单位
引入了线程之后,不仅进程之间可以并发执行,线程之间也可以并发运行
注意,引入【线程】之后,进程不再是CPU调度执行的最小单位!
而只是除了CPU之外的系统资源的分配最小单元(也就是打印机、音频播放器、摄像头......等等I/O设备的资源,是给进程的,而不是给线程的)
二、进程、线程、并发、并行这些关系
那么本人在学到这一章隐约就想起了两个知识点:
- 1、我在第一章讲过的【并发】和【并行】
- 2、我上一章讲的【进程】
知识点回顾:
1、【并发】和【并行】概念
2、还没引入线程的传统【进程】运行机制
那么引入了【线程】概念之后,就方便理解为什么单核CPU各个程序的并发运行,多核CPU是并行运行了
首先我们把进程比作各个正在生产产品的工厂,然后线程是里面工作的机器人,CPU是一个超大型发电机,【单核CPU就是只有一条充电线的发电机、 多核就是有多条充电线的发电机】
;
传统的进程模式下,CPU执行调度的单位是进程,所以只能串行的分时的执行一个一个进程,但是引入了线程之后,【线程】才是CPU调度的单位了,所以即使单核CPU也能够分时并发地执行各个进程里的线程(只不过每次同一时刻还是只能执行一条线程,所以是并发)
但是如果是多核CPU,那么他就有能力同时执行多个进程里的多个线程了,那想一下多个 "机器人" 同时充电工作,不就实现了并行了吗
三、线程的实现方式
1、线程库
前面第一大点我提过,python、java都有他们各自在代码里创建线程的方式,那么调用这些方法或者对象的根源,是python、java等编程语言开发者自己写好了一套【线程库】
例如Python
2、两种实现方式
1)用户级线程(ULT, user level thread)
就是 "从用户视角能看到的线程",操作系统看不到线程
这就是基于传统进程实现的,操作系统只调度进程,所以所谓的 "线程" 只是程序员自己调用自己写的 "线程库API" 来实现,不需要操作系统来介入,所以也自然不需要CPU切换用户态、内核态
这样虽然可以避免CPU频繁变换状态,但是并不是真正意义的多线程,因为假设现在有多核CPU,但是由于操作系统只看得到进程,所以CPU还是只能分别运行一个进程而已,而不是真的同时执行多个进程的各个线程。
而且假设一个进程的某一个线程堵塞了,整个进程就堵塞了,影响别的线程正常工作.....
2)内核级线程(KLT, kernel level thread)
就是操作系统来调度操作线程
这就是引入了线程的进程,CPU之间调度运行线程,那就需要操作系统来介入管理,那也自然要由CPU从用户态切换到内核态
然后操作系统会为每个内核级线程创建各自的数据结构------------ "线程控制块"(TCB),操作系统通过对TCB管理来管理线程
虽然要CPU频繁变态,但是不会因为一个线程阻塞而影响别的线程工作;
而且是真正意义上的并发运行多线程,多线程也真正可以在多核CPU上并行运行
3)当然还有组合他两的方式
。。。。。。没啥说的,知道有就行
3、多线程模型
那么对于支持上面第2种的内核级线程的系统,根据【用户级线程】和【内核级线程】的映射关系,又可以分成这么几种模型
1)一对一
2)多对一
3)多对多
四、线程的状态与转换
和进程一样,线程也有自己的几种状态,那么这里的知识点因为大量跟进程重复,所以用进程的思维去理解即可
线程就关注【就绪】【运行】【阻塞】这三个状态,那其实什么时候用什么状态跟进程是一个道理的,所以就不多解释了,只不过我们这里不关心【创建态】和【终止态】
然后关于操作系统如何控制管理线程,也跟进程的原理一样:
进程的一些信息、id标识、状态都被记录在一个数据结构------PCB中,操作系统创建这么一个数据结构来方便管理每一个进程
而线程也一样,现在引入了线程,那么操作系统工作的重心就换成线程了,因此每个线程创建都会对应创建一个数据结构------【TCB】用来存储这个线程的信息