众所周知,新事物的产生必然是有他存在的意义,放在计算机的世界里,一个新的模型或工具的产生必然是用来解决某些实际问题的,那回到我们的今天要讲的主题——GMP调度,它的出现是用来解决什么问题呢?
调度器的由来
为了说清楚GMP调度,我们先了解一下调度器的由来。
进程串行的问题
首先,最初的单进程时代是不需要调度器的,因为一切的程序在操作系统上只能串行发生。
但是紧接着就带来两个问题:
- 单一的执行流程,计算机只能一个任务一个任务处理。
- 进程阻塞所带来的CPU时间浪费。
那能不能让多个进程“同时”执行呢,为了解决这两个问题。就出现了“多进程并发”的概念。也就是当一个进程阻塞的时候,切换到另外等待执行的进程,这样就能尽量把CPU利用起来,CPU就不浪费了。
实现多进程并发(调度器的出现)
在多进程/多线程的操作系统中,就解决了阻塞的问题,因为一个进程阻塞cpu可以立刻切换到其他进程中去执行,而且调度cpu的算法可以保证在运行的进程都可以被分配到cpu的运行时间片。这样从宏观来看,似乎多个进程是在同时被运行。
新的问题
进程拥有太多的资源,进程的创建、切换、销毁,都会占用很长的时间,CPU虽然利用起来了,但如果进程过多,CPU有很大的一部分都被用来进行进程调度了。
CPU调度切换的是进程和线程。尽管线程看起来很美好,但实际上多线程开发设计会变得更加复杂,要考虑很多同步竞争等问题,如锁、竞争冲突等。
提高CPU的利用率(协程的出现)
进一步,工程师们就发现,其实一个线程分为“内核态“线程和”用户态“线程
内核线程依然叫“线程(thread)”,用户线程叫“协程(co-routine)".
既然一个协程(co-routine)可以绑定一个线程(thread),那么能不能多个协程(co-routine)绑定一个或者多个线程(thread)上呢
解决办法
N个协程绑定1个线程
- 优点:就是协程在用户态线程即完成切换,不会陷入到内核态,这种切换非常的轻量快速。
- 缺点,1个进程的所有协程都绑定在1个线程上,一旦某协程阻塞,造成线程阻塞,本进程的其他协程都无法执行了,根本就没有并发的能力了
1个协程绑定1个线程
优点:这种最容易实现,协程的调度都由CPU完成了,不存在N:1缺点
缺点:协程的创建、删除和切换的代价都由CPU完成,有点略显昂贵了
M个协程绑定N个线程
是N:1和1:1类型的结合,克服了以上2种模型的缺点,但实现起来最为复杂
线程由CPU调度是抢占式的,协程由用户态调度是协作式的
小结
至此,我们就基本了解了调度器是怎么来的,为什么要有调度器(解决阻塞问题实现进程并发执行),以及调度器的优化(M:N模型)在避免阻塞问题的同时尽量减少进程切换开销,提高CPU使用效率。
被废弃的调度- GM调度(没有P)
众所周知,技术的发展不是一蹴而就的,一直在随着时代不断革新和演进,Go的调度器也是如此,今天家喻户晓的GMP调度也是改进后的结果。
Go目前使用的调度器是2012年重新设计的,因为之前的调度器性能存在问题,所以使用4年就被废弃了,那么我们先来分析一下被废弃的调度器是如何运作的?
M想要执行、放回G都必须访问全局G队列,并且M有多个,即多线程访问同一资源需要加锁进行保证互斥/同步,所以全局G队列是有互斥锁进行保护的。
GM调度的缺点:
- 创建、销毁、调度G都需要每个M获取锁,这就形成了激烈的锁竞争。
- M转移G会造成延迟和额外的系统负载。比如当G中包含创建新协程的时候,M创建了G’,为了继续执行G,需要把G’交给M’执行,也造成了很差的局部性,因为G’和G是相关的,最好放在M上执行,而不是其他M'。
- 系统调用(CPU在M之间的切换)导致频繁的线程阻塞和取消阻塞操作增加了系统开销。
为了解决以上问题,必须得引入一套新的机制,此时GMP便应运而生了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。