协程本质:
go协程本质上还是用线程来运行代码,只是在多线程上增加了调度器,通过调度器让每一个线程可以执行多个协程。
实现原理:
go协程使用GPM调度模型实现,具体内容如下:
G goroutine协程
P process 调度器,为每一个m分配g,
M machine 对应操作系统的线程,g的真正执行者。
P的数量默认是CPU核数,也可以通过GOMAXPROCS来指定数量,每个P都会维护一个runq队列,用于保存G,P会从队列头获取G交给M执行,执行完后放入到队列尾(如果需要继续执行),通过GPM模型实现了多个协程并行(不是并发)执行,可以最大限度地利用到CPU。
协程优点:
协程相对于多线程有哪些优点呢?
1.上下文切换更轻量
触发go上下文切换有两种场景,1是协作式抢占引起的协程切换,2是锁阻塞\IO阻塞\channel阻塞,两种场景保存的上下文数据结构都是g.sched,它的数据结构如下:
与栈相关的SP和BP寄存器
PC
用于保存函数闭包的上下文,也就是DX寄存器
相比多线程协程切换只需要修改少数寄存器内容,所以更轻量,但一个线程使用到的寄存器总大小非常有限,就算全部内容更新也不会消耗多少资源, 但为什么大部分人都会说协程减少了上下文切换带来的开销? 这里就要看上下文的定义了,如果不仅包括协程执行时所需要的数据,还包括内核态与用户态的切换则这句是对的。
2.节省了内核态与用户态的切换
用户态切换与内核态之间切换一次可能花费1000cycles,线程切换由操作系统内核完成,所以需要从用户态切换到内核态然后再切回用户态,而协程则不需要进行状态的切换。
下面这段话测试结果描述了上下文切换带来的损耗:
Since all the context switching is happening at the application level, we don’t lose the same ~12k instructions (on average) per context switch that we were losing when using Threads. In Go, those same context switches are costing you ~200 nanoseconds or ~2.4k instructions.
摘自:https://www.ardanlabs.com/blog/2018/08/scheduling-in-go-part2.html
3.提高了IO类任务的效率?
CPU执行线程是抢占式的,GO协程也是协作抢占式,工作原理都一样,都是为每次执行分配固定的时间片,如果只从这一点看,它们执行io类任务效率不会有多大差别,但实际上产生效果还是有区别的,这个后面会提到。
协程引发的思考
1.操作系统需要支持协程吗?
协程能减少线程切换带来的开销,那操作系统有必要开发一种支持协程的线程吗,或者用其它方式支持协程的API? 如果这样做确实可以降低用户或各语言的开发难度,那这种方案可行吗?或者有必要吗?
个人觉得虽然可行但没必要,这样会提高系统的复杂性,尤其是协程队列的维护,这样的功能更贴近业务应该让各开发语言来实现,而不是底层的操作来实现,那这里又引发了另外一个思考:java需要支持协程吗?
2. java需要支持协程吗?
暂时不会支持,以后可能会有,理由是JAVA的线程池同样也有协程带来的开销减少好处,虽然写协程代码更简洁方便,但JDK1.8开始函数式编程的完善让我们创建线程更快捷了。不过如果JAVA支持协程确实会带来一些执行效果上的改变,例如前面提到的IO类任务,在线程池方案中,只有当前活跃的线程会拿到CPU时间片,如果这些线程是IO类会导致正在排队的其它任务长时间不能执行,这样大大降低了CPU利用率,虽然提高线程池活跃数能解决一部分问题,但还是做不到像协程那样每个任务都能公平分配到时间片,所以JAVA实现协程还是有一点点价值的,同样运行在java虚拟机上的Kotlin就支持协程。
求助:
因本人的知识储备与时间有限,有些问题还需要求助网友:
进程内线程切换会触发内核态与用户态的切换吗?
在网上查资料用户态切到内核态有以下三种情况:
1.当程序使用到系统内核的数据或者程序时就需要进入内核态
2.硬件出现异常,会优先执行系统程序
3.程序出现系统级错误
进程内线程切换一般是时间片用完,这种情况并不属于以上三种,那它会触发内核态与用户态的切换吗
问题知乎地址:https://www.zhihu.com/question/451104554
大家帮忙在知乎上回答这个问题,也可以钉钉我一起讨论:jeff07,当然上面笔记有描述错误也欢迎指正。
名词解释:
协作式抢占:sysmon 协程标记某个协程运行过久, 需要切换出去, 该协程在运行函数时会检查栈标记, 然后主动调用GoSched()让出CPU。
SP:stack pointer register 栈指针寄存器(指向栈顶)
BP: base pointer register 基指针寄存器
DX: 通用寄存器中的数据寄存器
参考:
https://www.zhihu.com/question/20862617
参考重要摘要: