从今天开始看《并发编程的艺术》,然后留下学习的笔记,是学习,是提问,是散发思维,是形成自己的知识体系。抱着学习研究的态度,去学习这本书,去深挖一门技术。看完一遍,还有一遍,如此反复。发布出来是因为,技术需要交流,如果我有写错的地方,希望朋友们可以狠狠批评,我认真改过。好,废话不多说,马上开始头脑风暴。
上下文切换,时间片,线程调度算法
1 什么是上下文切换,上下文切换的原理是什么?
1.1 cpu调度算法干了什么?
在操作系统层面,有一个概念叫模式切换,从用户态到内核态,或者从内核态到用户态。一般上下文切换指的是进程上下文切换,或者线程上下文切换。因为进程运行过程当中,会产生,局部变量,进程的方法栈,当cpu调度系统,从一个时间片切换到另一个有效的时间片时,会保存现场数据(上下文数据),局部变量表,程序运行到哪一行,进入PC(程序计数器)等一些数据到内核进程的堆栈中。 实际上,是因为cpu内部有一个计数器,当计数器倒计时到0
,那么代表时间片用完了,这个时候,会产生一个中断去触发,cpu调度算法
,去重新分配时间片给在就绪队列中的进程。如此循环往复。
1.2 CPU内部的MMU干了什么?
1.2.1首先MMU的主要功能是什么?
- 地址转换
- 权限管理
- 交换分区
首先引入两个概念,虚拟地址(VA)和物理地址(Virtual Address , Physical Address)
。如果处理器没有MMU,或者有MMU但没有启用,CPU执行单元发出的内存地址将直接传到芯片引脚上,被内存芯片(以下称为物理内存,以便与虚拟内存区分)接收,这称为物理地址(Physical Address,以下简称PA
)如果处理器启用了MMU,CPU执行单元发出的虚拟地址将被MMU映射转换成PA。那么中间肯定有一个像对照表一样的东西,从VA到PA。如果是32位处理器,那么他的地址总线是32位的,那么代表他的寻址能力是4G。但是实际上,这个表是存在于CPU内部一个叫TLB(Translation Lookaside Buffer 一般叫页表缓存,这个是页表缓存,就有命中hit和miss,在物理内存的内核区域还有完整的页表)结构如下图所示:
image.png
那么他不可能是这么大的一个表,肯定空间不够。那他如何寻址?
答案是通过将内存分页。通过页帧号+页内偏移
来寻址,一般32位处理器是一页32KB。寻址方式 如下图。
然后回到原先的问题,MMU干了什么?在进程切换的时候,这里他要清空一些TLB中的映射条目,因为从一个虚拟地址空间到另外一个地址空间。有些条目,已经失效了。但是内核进程由于被用户进程锁共享,一直在内存的某个区域不动,所以这部分的话,不需要刷新。cpu缓存中的数据,也要被清空,设置为invaliad。这就导致,进程切换的时候,很多东西都是未命中,需要重新加载。切换时图片如下所示:
image.png
2 疑问?
什么时候会触发上下文切换? (这里的疑问??)是和进程一样的吧,时间片到了我的猜测,只有在系统时钟周期到了,或者由于中断,陷入内核态,才会触发调度算法。
《并发编程的艺术》书中显示:
多线程竞争锁时,会引起上下文切换
3 如何减少上下文切换
- CAS,无锁并发编程。
- CLH 队列,采用
唤醒传递机制
,避免大量线程同时唤醒,引起一个惊群效应
- 分段锁,或者锁的离散化 ,就像jdk1.7 的ConcurrentHashMap .采用更强的离散函数。
- 在一定量的任务前提下,合理使用线程池。使得在用的线程得到更大程度的利用。比如
工作窃取fork/join
,线程的重复利用。虽然说本身并不能减少上下文切换,但是参数上下文切换的线程少了,那就间接的减少了上下文切换。 - 协程。
3.1 协程是什么?
线程不同状态之间的转化是谁来实现的呢?是JVM吗?
并不是。JVM需要通过操作系统内核中的TCB(Thread Control Block)模块来改变线程的状态,这一过程需要耗费一定的CPU资源。
多线程编程的耗性能操作:
- 同步锁
- 线程的状态切换
- 线程的上下文切换
但是,协程大大减少了上面所涉及的性能消耗。
协程,英文Coroutines,是一种比线程更加轻量级的存在。
协程不是被操作系统内核所管理,而完全是由程序所控制(也就是在用户态执行)。
这样带来的好处就是性能得到了很大的提升,不会像线程切换那样消耗资源。
3.2 协程的应用
- Python语言
python可以通过 yield/send 的方式实现协程。在python 3.5以后,async/await 成为了更好的替代方案。 - Go语言
Go语言对协程的实现非常强大而简洁,可以轻松创建成百上千个协程并发执行。
网友评论