go协程学习笔记

协程本质:

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://mcll.top/2020/04/14/go%E7%9A%84%E5%8D%8F%E7%A8%8B%E4%B8%8A%E4%B8%8B%E6%96%87%E5%88%87%E6%8D%A2/

https://www.zhihu.com/question/20862617

参考重要摘要:


你可能感兴趣的:(go协程学习笔记)