众所周知,新事物的产生必然是有他存在的意义,放在计算机的世界里,一个新的模型或工具的产生必然是用来解决某些实际问题的,那回到我们的今天要讲的主题——GMP调度,它的出现是用来解决什么问题呢?

调度器的由来

为了说清楚GMP调度,我们先了解一下调度器的由来。

进程串行的问题

首先,最初的单进程时代是不需要调度器的,因为一切的程序在操作系统上只能串行发生。

但是紧接着就带来两个问题:

  1. 单一的执行流程,计算机只能一个任务一个任务处理。
  2. 进程阻塞所带来的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便应运而生了。


我爱学习
4 声望1 粉丝