下面我们来讨论线程
在刚才我们讨论进程的时候呢
每个进程内部它的指令执行呢
是有一个叫指令指针的寄存器来描述
当前这个进程执行到什么地方
但是在我们实际使用的时候呢
一个进程内部我也可能会希望
它有更好的并发性
那这就是我们在这里
引入线程的原因
那在这儿我们先讨论
引入线程我们通过一个实例来说
为什么我们要在一个进程内部
再进一步提高它的并发性
好 然后说什么是一个线程
在这儿呢我们给入这样一个例子
说我有一个应用
这个应用呢是播放音频的 MP3
那它的功能呢包括这样几个方面
一个呢是说我从音频文件里头
读出相应的数据
然后通常情况下
我们的音频文件是压缩的
然后我需要对数据进行解压
解压之后呢
我送到声卡去进行播放
这是它要进行的三步
这三步呢我们怎么来实现呢
这个地方给出了一种
用单进程的方式来实现的
也就是说我在这个进程内部呢
做了一个循环
这个循环里头呢读数据
解压然后播放一步一步来
这个程序能很好的工作吗
那你说我在这里头
读磁盘足够速度快
然后解压足够速度快
那我解压完了之后给这边播放
播放还没有播放完之前
我下一部分的解压数据
能跟得上的话
这个程序是可以很好工作的
但是如果说你细看下去的话
这个程序会在什么地方有问题呢
第一部分它需要
频繁的进行IO操作 进行读写
而第二部分呢
解压它主要是用CPU来算
这两个部分呢是交替进行的
实际上我们在系统里呢
这两个部分是
应该可以并发进行的
但实际上在这儿呢这件事情
做起来就比较困难了
如果说我在负载比较重的时候
或者说我的处理能力比较弱的时候
那这时候呢
播放声音的连贯性就会受到影响
然后它资源的利用效率呢
也会比较低
好 因为这样的话
我就没有办法干别的事情了
因为要不然的话稍微慢一点
好 最后我播出来的声音
也就是说在声音上一次给的
数据缓冲没播完之前
如果说你下一波数据没过来的话
那这地方声音它就会不连贯
那说这样播不好
那我们是不是可以把这个程序改成
由多个进程来一块写呢
好 那这时候说我把它改成
三个进程来实现
第一个进程是
管读数据读到内存里头
第二个进程呢是管解压缩
第三个进程管播放
那第一个读完数据的时候给第二个
第二个解压的过程中
由于操作系统的并发性
我可以切换运行
好 那这几个呢就可以
在你读数据的等待阶段
我就可以进行解压了
好 这样的话说起来
它们实际上可以并行
但这时候呢
又有一个问题说
我在这个地方
在做这件事情的时候
它们之间如何来通讯
如何来共享数据
那我们在前面大家还记得
我们在讨论进程的时候
我们讨论进程控制块
主要的一个目的是说让
各个进程之间能很好进行隔离
而现在呢我们这几个隔离
不是它最主要的要求
它们需要的要求是
它们之间能更好共享数据
而这个要求和我们最开始设计进程
多进程的时候
它的目标呢是有一定矛盾的
好 那在这种情况下
我们 它的开销也是相对较大的
因为两个进程之间进行通讯
通常情况下都通过
系统调用要从内核里绕一圈
那如果说这些都是在一个进程内部
那这件事情就会好很多
正是由于这种缘故
我们希望在这里呢
得到这样一种对进程进行改进
在进程内部增加一类实体
使得这一类实体呢
在一个进程内部呢它可以并发执行
有多个指令指针在执行
同时呢它们之间共享的相同地址空间
这样呢它们的信息交流呢
会比原来更方便
因为这时候隔离
不是它们之间要解决的问题
因为它是密切相关的一组执行流
好 而正是由于这种需求
就导致了我们线程的引入
好 那这种思路目标我已经有了
那我怎么来做呢
这就是我们这里说的线程的概念
线程是进程的一部分
它描述指令执行流的状态
好 那它在这里头我把进程当中的
关于执行流的信息呢剥离出来
构成我的线程
但它仍然是进程的一部分
这种剥离有什么样的好处呢
剥离它就可以为并发
在一个进程里头
有多个线程啊提供可能
好 那这样的话把指令执行流的
最小单位变成是线程
然后CPU调度呢变成是线程
好 有了这两条之后
那我在进程内部的并发呢
就变为可能了
好 有了这种变化之后
我们原来的进程控制块
会是啥变化呢
好 原来的进程呢
变成是资源分配的角色
那这是我们的地址空间
我们大家可以看到的
在这里头跟我们原来比的话
跟指令流相关东西就不放在进程里头了
那这里头跟它相关什么
就是堆栈
每个指令流它有函数调用的时候
它必须有自己独立的堆栈
好 那这样我把它剥离出来
变成是我们这里的线程的组成部分
那线程呢是来负责处理机调度的对象
好 那么在这儿呢 我有若干个线程
我在这里有各自线程 有自己堆栈
好 那把相关的关于执行流的状态
的信息呢变成是线程控制块
好 那这个线程控制块呢
也从属于我们的进程控制块
用指针指向它
好 那这时候我就可以有多个指令指针
多个堆栈
和多个CPU里的寄存器的现场保护
因为这个现场保护是和执行流相关的
好 有了这样一个概念之后呢
我们就可以在一个进程内部呢
提高它的并发程度
这个道理说完之后呢
它大致想起来是可以的
那这时候我们会做一些什么样的变化
好 这是我们原来描述的进程的信息
有代码数据 文件 寄存器 堆栈
这是我们原来有的
那在原来里头我只有一个指令指针
所以这部分只有一份
我改完之后变成什么样子呢
这是多线程的进程 那在这里头呢
代码数据打开文件
仍然是和进程属性相关的
它是进程的属性
而跟执行流相关的
我可以变成多份
这时候呢寄存器堆栈
变成是我们线程的属性
线程是进程里的一部分
这时候这样做变化之后
我就可以有多个线程在一个进程内存在了
好 那有了这样做之后呢
我们从道理上来讲我们多线程就可以
很好的支持进程内部的并发
好 这是我们说线程是进程减去资源共享
剩下部分那就是我们执行流的信息
好 那这样的话
我们这样做完之后
线程的引入有什么样好处呢
一个进程内有多个线程
线程之间呢可以并发执行
线程之间呢
可以共享地址空间和文件等资源
这样共享会变得方便
并发性会变得好
那它有啥麻烦呢
因为你在一个进程内部的多个指令流
那这时候呢它们之间的隔离就没有了
好 如果说一个线程行为有异常
它改了另一个线程的相关信息
那么这时候你的整个进程地址空间里的
这些线程就都运行不了了
好 这是呢但是说这种缺点呢是说
我们不是重点关注的
并且我认为各个线程之间
是相互合作的
所以它隔离的成分
我们不要求那么长
好 那有了这样之后我们来说
现在我们在历史上这个变化的过程
这个多线程思路是很好的
那我们看一下这个思路
它在历史上出来的时候
它会是怎样一个变化的过程
也就是说我们现在这些系统里
是如何逐步来支持这个多线程的
最早的计算机系统呢
里头只有一个物理的
指令指针寄存器
那这就是我们最早的单进程系统
比如说像我们的MS-DOS整个系统里
只有一个指令指针流在跑
也就相当于这时候
我们关于进程管理这些机制呢
这里头都没有
好 然后我们说希望提高它的并发性
好 这时候加了多进程系统
那这时候呢我们传统的UNIX系统
就是从这么做起来的
这时候我们在进程切换
和进程之间的资源隔离
那都是在这个阶段给出来的
那有了这个阶段之后
我们想进一步提高
这个一个进程内部的并发性
就变成了我们现在的多线程系统
那在一个进程
同时有多线程和多进程
那就是我们现在的
这种系统里呢支持的情况
实际上在这里头我们还有一类系统
在现实的生活当中也是存在的
这就是单进程多线程系统
那这一些系统呢
通常情况下是对于
那些对并发执行要求高
对信息共享要求高的
但是对安全隔离
要求低的这些系统里头
比如说像我们的路由器里头
路由器里头呢
它是负责分组的转发
路由表的查询
这些是密切相关的
但是它们之间呢
需要很好的这个信息共享
但是由于他们都是一家做的
并且路由器里呢不支持
你第三方往顶上加自己的程序
好 那这时候呢它的隔离
就做的比较弱
好 那这样的话不做隔离的结果就是
我这里只支持一个进程里头
有很多的线程
比如说像我们用到的PSOS操作系统
它在路由器上用的它就有这种特征
好 那有了这个之后 那我们来看
进程和线程
它们实际上什么样的关系呢
进程是资源分配的单位
线程是处理机调度的单位
好 进程和线程呢
一个是拥有完整的资源
一个是只拥有跟执行流相关的资源
那这样一来的话我们线程呢
它所要保存信息就少了
那在同一个进程里头
它们相互之间切换的时候
你需要保存的信息就少了
这个时候它的速度就会快
好 每一个原来说跟执行相关的
这些进程状态
现在都转成是线程的状态了
所以线程有就绪 等待 执行 运行
几种基本的状态
它们之间的状态的转换
跟我们前面讲的进程呢是完全一致的
好 有了这些之后
那我们的线程呢
就会有很好的特征
那这时候它减少
并发执行的时间和空间开销
时间开销呢是指说创建终止切换
它的时间都会比原来少
这里所说的创建终止切换
是指在一个进程内部的线程的创建
如果说你创建一个
新的进程里的新线程
那和原来的进程创建呢
开销是相当的
甚至于有可能会多一点
好 然后说我在这里头呢
它们之间的共享呢
会比原来更方便
好 那到这个地方呢
我们就说清楚了线程的基本概念
那下面我们会去说如何来实现它