Golang 协程调度知识点总结

Golang 协程调度知识点总结

一、用户态、内核态、系统调用、中断
现代cpu通常有多种特权级别,一般来说特权级总共有4个,编号从Ring 0(最高特权)到Ring 3(最低特权),在Linux上之用到Ring 0和RIng 3,用户态对应Ring 3,内核态对应Ring 0

系统调用,例如操作文件、进行网络通讯或者申请内存资源等

中断一般有两个属性,一个是中断号,一个是中断处理程序。不同的中断有不同的中断号,每个中断号都对应了一个中断处理程序。在内核中有一个叫中断向量表的数组来映射这个关系。当中断到来时,cpu会暂停正在执行的代码,根据中断号去中断向量表找出对应的中断处理程序并调用。中断处理程序执行完成后,会继续执行之前的代码。

二、多路复用技术(三种常见的实现方式:poll、select、epoll,由于epoll是最新技术,性能要比其他两个老技术好太多,因此现在基本只会使用epoll)有了这个东西,协程才有用武之地

1.select的fd_set是一个静态的数组,所以支持的文件描述符数量有限(默认为1024),而 poll和epoll 传入的相当于一个动态数组(指针 + 元素个数),所以支持的文件描述符数量没有限制

2.如果在 select/poll 调用之前,如果没有事件发生。select/poll将阻塞,进程休眠,知道超时或者被中断。内核将 fd_set/ pollfd 结果拷贝到用户态,同时用户态调用返回。用户态遍历所有监控的文件描述符,检查返回结果

3.对于epoll,内核使用红黑树来快速的添加删除需要监控的文件描述符,同时基于事件驱动,文件描述符 fd 有事件发生时,内核的回调函数会将该 fd 加入到内核维护的 ready list 内。所以调用 epoll_ctl 时,内核只需要去检查 ready list 并拷贝结果到用户态即可

水平触发通知:如果文件描述符上可以非阻塞地执行I/O系统调用,此时认为它已经就绪,触发通知。
边沿触发通知:如果文件描述符自上次状态检查以来有了新的I/O活动(比如新的输入),此时需要触发通知。

三、GPM模型

M:一个用户空间线程,同时对应一个内核线程,类似posix pthread
P:代表运行的上下文环境, 有个一个runqueue(里面存放着goroutine)
G:goroutine,即协程

P的数量由GOMAXPROCS()来设置,默认为CPU的核数

当一个OS线程M0陷入阻塞(系统调用)时,绑定它的P会转而运行M1,M1可能是正被创建,或者从线程缓存中取出。

当MO返回时,它必须尝试取得一个P来运行goroutine,如果没有拿到的话,它就把goroutine放在一个global runqueue里,然后自己睡眠(放入线程缓存里)。

P也会周期性的检查global runqueue并运行其中的goroutine

P何时创建:在确定了P的最大数量n后,运行时系统会根据这个数量创建n个P。

M何时创建:没有足够的M来关联P并运行其中的可运行的G。比如所有的M此时都阻塞住了,而P中还有很多就绪任务,就会去寻找空闲的M,而没有空闲的,就会去创建新的M。

M的最大数量,默认10000,也可以通过runtime/debug中的SetMaxThreads函数去重新设置

Gwaitting:内部channel或者mutex阻塞,会触发P runqueue的轮换
Gsyscall:调用了syscall(没有事件准备的Nonblocking IO除外),会触发P和M的解绑

加餐:Golang GC机制

GoV1.3- 普通标记清除法,整体过程需要启动STW,效率极低。
GoV1.5- 三色标记法, 堆空间启动写屏障,栈空间不启动,全部扫描之后,需要重新扫描一次栈(需要STW),效率普通
GoV1.8-三色标记法,混合写屏障机制, 栈空间不启动,堆空间启动。整个过程几乎不需要STW,效率较高。

由于没有分代回收,因此对于会产生小对象的case,尽量用sync.Pool

详细文章教程:
https://studygolang.com/articles/27243?fr=sidebar

你可能感兴趣的:(Go,golang,协程)