尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Go GMP 调度模型:为什么 Go 可以轻松创建百万级 Goroutine?

Go GMP 调度模型:为什么 Go 可以轻松创建百万级 Goroutine? Go GMP 调度模型为什么 Go 可以轻松创建百万级 Goroutine文章目录Go GMP 调度模型为什么 Go 可以轻松创建百万级 Goroutine早期单进程操作系统多线程/多进程操作系统改进版 多线程/多进程操作系统这里是N:1的关系如果是M:N每个线程绑定一个cpu协程调度器去调度协程给空闲线程Go语言对协程的处理Go早期对调度器的处理Go对老调度器的改进---GMP模型GMP的设计策略复用线程、利用并行、抢占、全局G队列复用线程利用并行全局G队列早期单进程操作系统只能一个进程一个进程的完成成为串行缺点1如果一个进程阻塞后面的进程就会一直等待2CPU利用率不高多线程/多进程操作系统一个时间段内同时执行多个任务(注意是同一时间段内)称为并发这个解决了单进程操作系统的阻塞问题当前进程阻塞时就切换另一个进程运行缺点1高消耗调度cpu线程之间相互切换也是有时间成本的切换次数很多时cpu的大多数时间就浪费在切换上面了此时cpu利用率不高2高内存占用比如在32位操作系统(OS)里面一个进程占用虚拟内存4GB线程占用内存约4MB占用空间还是比较高的显然在多线程/多进程操作系统中一个程序运行的一个实例时一个进程一个进程有多个线程当然一个程序也可以有多个进程比如我们在电脑上点击两次QQ会出现两个界面这就是两个QQ进程每个进程内部有多个线程负责不同的工作比如一个线程负责界面渲染、一个负责网络通信等改进版 多线程/多进程操作系统上一版本操作系统线程不仅有内核空间也有用户空间cpu要管这两个空间其实cpu可以只专注于内核空间所以在改进版中把内核空间和用户空间分开了即用户线程和内核线程用户线程命名为协程内核线程命名为线程这时候可以在线程和协程之间加一个调度器来实现协程之间的不断切换但是线程还是原来的那个这样就解决了上一版操作系统的高消耗cpu调度问题这里的协程之间不断切换是在用户态由用户调度器完成不需要操作系统进行线程调度因此比线程切换开销低这里是N:1的关系弊端如果三个协程都阻塞了那么这个线程就也阻塞了cpu得不到利用如果是1:1的关系协程之间的切换也就是线程之间的切换那么和上一版的操作系统一样有高消耗cpu调度如果是M:N每个线程绑定一个cpu协程调度器去调度协程给空闲线程M:N模型通过多个线程承载多个协程当某个协程阻塞时调度器可以将其他可运行协程调度到空闲线程上提高线程利用率相比于上一版操作系统线程阻塞仍然存在但是调度单位从线程变为了协程并且最关键的部分在于协程调度器协程调度器属于用户空间不同的编程语言协程调度器做的越好cpu的利用率就越高后面我们讲Go语言对协程的处理而内核空间是由操作系统决定的这是没法修改的Go语言对协程的处理用户空间有协程、协程调度器Go的改进名字从co-routine改为goroutine1针对用户空间的协程内存改为几KB — 可以大量 解决了高内存占用问题2针对用户空间的协程调度器灵活调度 — 可常切换 解决了高消耗cpu调度问题下面我们来说 Go对协程调度器的处理Go早期对调度器的处理Ggoroutine 协程Mmachine 线程全局队列有一个锁调度器获得锁之后调度G给M去执行执行完之后释放锁把G放回队列末尾弊端1锁竞争等待锁、获取锁、释放锁···还可能造成死锁激烈的锁竞争也会导致效率低下2M转移G会降低cpu利用率当M0执行一个G阻塞的时候需要进行调度切换寻找其他可运行的G。频繁的G切换会增加调度开销使调度任务的时间增加运行任务的时间减少从而降低了cpu的利用率Go对老调度器的改进—GMP模型之前只有G、M线程M负责执行G也负责从全局队列中获取和调度G还要完成锁的相关操作所以加入了PP存在的意义是P 是为了把“调度资源”从 M线程中剥离出来让 Go Runtime 可以自己管理 Goroutine之前Go Runtime 负责整个 Goroutine 系统包括创建 G、管理 G 状态、调度 G 执行现在将原本集中在 M 和全局调度器中的调度资源抽离出来让它管理而不是完全依赖操作系统线程调度M只需要绑定P执行G只需要执行GP相当于一个处理器管理一个本地队列本地队列里面包含多G并且包含部分运行时缓存资源P的一个较严格的定义P 是用于执行 Goroutine 调度所必需的逻辑处理器资源它负责管理 Goroutine 队列以及调度运行所需的上下文只有拥有 P 的 M 才能够执行 Go 代码GMP的设计策略复用线程、利用并行、抢占、全局G队列复用线程两个机制work stealing、hand off1work stealing偷取机制顾名思义当某个线程空闲就会从其他队列里面偷取G来执行这里G3会被偷取到M2所绑定的P本地队列2hand off机制可以理解为放手机制如下图如果正在执行的G1阻塞了那么就要把当前P和它的本地队列交给别的M执行M1必须放手 hand off;由于M2不空闲就创建或者唤醒一个新的M把刚才的P和它的本地队列交给新的M![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/9e4ac9954b12453aae68ccac4266a8fc.png利用并行一个 P 在运行时通常需要绑定一个 M而 M 是操作系统线程需要被调度到 CPU 核心上执行。P 的数量由 GOMAXPROCS 决定它代表 Go 程序能够同时执行 Go 代码的最大并行数量而不是 CPU 核心本身多个 P 可以让多个 M 同时执行 Go 代码从而实现并行抢占G不会一直占有cpu因为是有时间限制的超过一定时间就把cpu抢占过来给别的G用全局G队列全局队列有很多作用这里只举一种情况在work stealing 时如果其他M所绑定的P本地队列都是空的那么可以从全局队列偷取参考B站视频https://www.bilibili.com/video/BV1gf4y1r79E?t1633.4p26
返回列表