路漫漫其修远兮
吾将上下而求索

go学习:goroutine并发调度模型

并发,编程里面最核心的主题,一直以来都是被开发者谈及最多的话题;go语言是通过goroutine实现并发编程,goroutine具有消耗资源低、运行效率高等特点;官方宣称原生goroutine并发成千上万不成问题。那么,理解goroutine的调度模型和工作原理,对于写go代码显得非常重要。

Goroutine调度器

当一个程序运行时,操作系统会为程序启动一个进程,可以把进程看作是一个包含了应用程序运行时需要用到的各种资源的容器;这些资源包括但不限于内存空间、句柄、线程。一个线程是一个执行空间,这个空间会被操作系统调度来执行代码。对操作系统而言,它的眼里只有线程,操作系统会在物理处理器上调度线程来运行。

goroutine,是Go语言并发编程的实现,是一种用户层轻量级线程或者说是类协程。Go程序对操作系统来说只是一个用户层程序,它甚至不知道goroutine的存在。Go程序本身是运行在一个或多个操作系统线程上,所以Go调度器需要将众多goroutine按照一定的算法调度到操作系统的线程上执行。这种在语言层面自带调度器的,称之为原生支持并发

Go调度器模型

  • 每个创建的G(Goroutine)会放到Go调度器的全局运行队列,调度器将队列中的goroutine分配到一个P逻辑处理器,并放入逻辑处理器本地的队列中,等待被执行;

  • 每个逻辑处理器P需要绑定到M,M会启动一个操作系统线程;

  • 粗糙地说调度就是决定何时哪个goroutine获得执行资源、哪个goroutine应该停止执行让出资源、哪个goroutine应该被唤醒恢复执行等;

G-P-M 调度模型由Go抽象出来的实现,最终形成了Go调度器的基本结构:

  • G(Goroutine) ,每个Goroutine对应一个G结构体,G存储Goroutine的运行堆栈、状态以及任务函数,可重用。G并非执行体,每个G需要绑定到P才能被调度执行。

  • P(Processor/逻辑处理器), 对G来说,P逻辑处理器相当于CPU核,G只有绑定到P逻辑处理器才能被调度。对M来说,P提供了相关的执行环境(Context),如内存分配状态(mcache),任务队列(G)等,P的数量决定了系统内最大可并行的G的数量(前提:物理CPU核数 >= P的数量),P的数量由用户设置的GOMAXPROCS决定,但是不论GOMAXPROCS设置为多大,P的数量最大为256。

  • M(Machine,OS线程抽象),代表着真正执行计算的资源,在绑定有效的P逻辑处理器后,进入调度循环;调度的机制大致是从全局队列、逻辑处理器P的本地队列以及wait队列中获取G,切换到G的执行栈上并执行G的函数,调用goexit做清理工作并回到M,如此反复。M并不保留G状态,这是G可以跨M调度的基础,M的数量是不定的,由Go Runtime调整,为了防止创建过多OS线程导致系统调度不过来,目前默认最大限制为10000个。

系统调用阻塞

当正在运行的goroutine需要执行一个阻塞的系统调用,如打开/读写文件;线程和goroutine会从逻辑处理器P上分离,该线程会继续阻塞,等待系统调用返回;

此时,逻辑处理器失去了用来运行的线程,调度器(runtime)会创建一个新的线程,并将其绑定到这个逻辑处理器上;

之后,逻辑处理器P会从本地队列选取另一个goroutine来运行。一旦阻塞的系统调用执行完成并返回,对应的goroutine会放回本地运行队列,线程会保存好,以便之后继续使用。

以上是从宏观的角度对Goroutine和基本调度过程进行的一些概要性的总结,Go的调度有很多复杂的抢占式调度、阻塞调度的细节,后续再去找相关资料深入理解。


分割线,更新:

goroutine是轻量级

栈是用来存储当前正在运行或挂起的函数的空间,操作系统会为每个线程分配固定大小(一般是2MB)的内存块做栈。2MB的固定空间,对于小小的goroutine是很大的浪费,对于复杂的任务来说又明显不够用。

  • 动态栈

goroutine的栈不是固定的,一开始以一个很小的栈空间(2KB)开启生命周期,栈的大小会根据需要动态伸缩。和操作系统线程的栈有同样作用,会保存当前正在运行或挂起的函数的本地变量。

  • 调度器的切换成本

线程会被操作系统内核调度到处理器上运行,每几毫秒会发生一次硬件计时器中断,当前线程需要让出CPU并将线程状态保存到寄存器中,CPU继续处理其他线程任务,在此线程下一次获得CPU执行时间,会从寄存器中恢复该线程上次的的状态并继续执行。线程在内核切换上下文是很慢的。

Go调度器是在其本身运行的用户层进行调度的,不需要进入内核的上下文切换,调度成本会低很多。

参考:https://zhuanlan.zhihu.com/p/57875135

未经允许不得转载:江哥架构师笔记 » go学习:goroutine并发调度模型

分享到:更多 ()

评论 抢沙发

评论前必须登录!