Goroutine调度器及面试精选

Goroutine调度器

Go语言在并发编程有着非常强大的能力,讲到调度器,我们的话题离不开操作系统、进程与线程这些概念,在学习操作系统时,线程是操作系统调度的最基本单元。

在没有学习Go语言之前,线程之间通信,通过内存共享可以实现,但是在调度时每个线程都会占用1M以上内存空间,还有恢复寄存器中内容也需要向操作系统申请或销毁资源,这样会有较大额外开销。

在Go语言中,不需要通过共享内存来通信,而是通过通信来共享内存,这也是Go语言最重要的编程理念。这样的话,Go调度器对Goroutine的上下文切换就减少额外开销了。

Go 调度器概念

  • G(Goroutine) — 表示 Go协程,它是一个待执行的任务或代表一个Goroutine对象;
  • M(Machine) — 表示操作系统的线程,它由操作系统的调度器调度和管理;
  • P(Processor) — 表示处理器,它可以被看做运行在线程上的本地调度器;

M必须拥有P才可以执行G中的代码,P含有一个包含多个G的队列,P可以调度G交由M执行。P的个数在启动时就决定,默认是使用等同CPU的数量,又因为M必须持有一个P才能运行Go代码,所有M个数一般也是等同于CPU个数,以达到尽可能的使用CPU而又不至于产生过多的线程切换开销,这样就大大降低操作系统和硬件的负载。P的个数,可以使用runtime.GOMAXPROCS()来设置个数,当在IO密集的场景下可以自行设置来提高性能。

Go 语言的调度器是一个非常复杂机制,看了Go源码,虽然有很多注释,但是对于工作经验不足或刚入门Go开发来说,我们还是需要多多的深研究,还有很多细节需要去学习,比如,调度器的设计原理:

  • 在早起0.x版本中Go语言调度器是单线程调度器,由G-M模型组成,还有P这个概念;
  • 经过不断优化与设计,到1.0版本就引入多线程调度器,但是出现全局锁导致竞争严重;
  • 到1.1版本,引入处理器P,然后就构成GMP模型;
  • 到Go 1.2 加入抢占式调度器;以及到1.14版本引入基于信号量的抢占式调度器

面试题:

  1. Go 中的GMP模型了解吗?
  2. GMP模型Go有几种状态?(空闲、待运行、运行中、系统调用、等待中、已停止及栈复制中)
  3. 为什么Go能提高性能,与进程和线程有什么区别?

    • 线程和进程会导致CPU额外开销大,切换线程上下文及申请销毁都需要额外开销;
    • 线程占用内存一般是固定为2M内存空间;
    • Go 栈内存是可变的,初始的时候一般为2KB,最大可扩展为1GB;
    • Go 自己实现的调度器,所以创建和销毁开销小;

你可能感兴趣的