goroutine的调度问题,同样也是我之前面试的问题,不过这个问题我当时并不是很清楚,回来以后立马查阅资料,现整理出来备忘。

有一些预备知识需要说明,就是操作系统中的线程。操作系统中的线程分为两种:内核线程和用户线程。用户平时使用的线程并不是内核线程,而是存在于用户态的用户线程。用户线程并不一定在操作系统内核中对用同等数量的内核线程。这里有三个模型:

1.一对一模型(1:1)

2. 多对一模型(N:1)

3. 多对多模型(N:M)

下面就先来谈谈这三种线程模型。

1.一对一模型(1:1)

对于支持线程的操作系统来说,一对一模型是最简单的一种线程模型了,一个用户线程唯一对应一个内核线程,但反过来却不一定,一个内核线程并不一定有对应的用户线程存在。这样一来,由于一个内核线程至多只对应一个用户线程,线程之间可以做到最大程度的并发,不同线程之间不会相互影响,比如一个线程阻塞了也不会影响到其他线程的执行。对于多处理器,一对一的线程模型效率更高。但是很多操作系统限制了内核线程的数量,如果采用一对一模型,用户线程的数量也会受到比较大的限制。而且很多操作系统的内核线程在调度时开销较大,这也会影响用户线程的效率。

2. 多对一模型(N:1)

多对一模型意味着多个用户线程对应一个内核线程,用户线程间的切换由代码控制,因为线程间切换的效率比较高(不用陷入内核区去切换)。不过如果其中一个用户线程阻塞了,则和它对应相同内核线程的那些用户线程也都会阻塞,因为内核线程是被共用的(且是绑定的),此时它无法抽身出来。而且增加处理器个数对于多对一线程模型帮助也不大,毕竟在这种情况下,一个线程阻塞,相关线程也跟着遭殃的事实和处理器个数关系不大。这种模型的好处是线程间切换开销低,且线程数量可以很多。

3. 多对多模型(N:M)

多对多线程模型可以说是上面两种模型的结合,也是最复杂的,它把多个用户线程对应到多个内核线程上,且很多时候不是唯一绑定的。因此一个内核线程在一个时间点可以对应0到多个用户线程。且在运行期间,系统可以根据线程执行情况做合理分配。比如用户线程1、用户线程2和用户线程3对应到一个内核线程1,如果用户线程1阻塞了,系统可以调度用户线程2和用户线程3到其他内核线程上去,这是个动态的过程。多对多线程模型的优势是可以让系统资源得到比较均衡的使用,用户线程之间互相影响比较小,且在多处理器上表现不错(虽然增加处理器个数对它性能提升可能不如一对一模型那么高),关键是它很灵活。

Golang的goroutine调度和多对多模型密切相关,Golang自己有自己的调度器scheduler。Golang的调度器内部有三个重要结构:M、P和G。

M: 代表内核线程。

G: 代表一个goroutine,它有自己的栈,指令指针和一些基本信息,用于被调度。

P: 代表调度的上下文,是Golang内部的调度器,负责让多个goroutine在一个内核线程上运行,它实现了N:1到N:M。

可以看到在某个时刻,一个M对应到一个P,一个P上有一个正在运行的G(蓝色的G),且这个P上可能还有多个G等待被调度(灰色的G),P维护着这个调度队列(runqueue)。P的数量可以通过GOMAXPROCS()来设置,它其实也就代表了真正的并发度,即有多少个goroutine可以同时运行。不过需要注意,GOMAXPROCS()的最大值是256。当要启动一个goroutine时,只需要用go function(args)即可,一但我们启动了一个goroutine,就会在runqueue队尾加入这个goroutine,P会负责调度这些goroutine。

那么如果在某个M被阻塞了呢?这时候就是N:M模型的关键之处了,此时P可以被安排到其他M上去执行,由于P内部维护着一些G的信息,这些G都有独立的栈和指令指针这些基本信息,所以可以很方便地直接换到另一个未被阻塞的M下。

上图描述了这种情况,在左边,G0正在运行,当G0由于系统调用被阻塞时,调度器会创建或者从线程缓存中取得一个线程M1,转投M1。当G0返回时,它必须获得一个P来执行,此时一般是先查看系统中有没有空闲的P,如果有,就获得一个P,用这个P来执行,如果没能获得一个P,这个G0只能暂时放置到一个全局的执行队列(global runqueue)中,它所处的线程M0也就sleep了。系统中的P们会周期性地检查这个队列,取出里面的G来运行。

如上图,还有一种情况是,某个M上的P被分配的G很快就被执行完了,这时M和P都处于闲置状态,无事可做。但也不可能看着其他的P忙碌自己不帮忙吧,此时会先去global runqueue中检查是否有G可以拿,如果并没有,只好尝试去其他P上“偷”一些G来执行,一般就是偷对方runqueue大小的一半,如果还是没偷到,那该只好sleep,该P放入一个闲置的P队列结构等待再次被调度。
 
这样处理,就保证了系统中的G能被有效快速地执行,充分利用了系统资源。

注:以上部分信息和图片来自The Go scheduler

谈谈Golang中goroutine的调度问题的更多相关文章

  1. golang中goroutine池的使用

    1. 概念本质上是生产者.消费者模型可以有效的控制goroutine数量,防止暴涨案例:生成一个随机数,计算该随机数每一个数字相加的和,例如:123:1+2+3=6主协程负责生产数据发送到待处理通道中 ...

  2. golang中goroutine

    1. 概念 goroutine 奉行通过通信来共享内存,而不是共享内存来通信 goroutine 是由go的运行时(runtime)调度和管理的 go程序会智能的将goroutine中的任务合理的分配 ...

  3. golang中goroutine协程调度器设计策略

    goroutine与线程 /* goroutine与线程1. 可增长的栈os线程一般都有固定的栈内存,通常为2MB,一个goroutine的在其声明周期开始时只有很小的栈(2KB),goroutine ...

  4. golang中的goroutine

    1. 概念 go中可以并发执行的活动单元称为goroutine当一个go程序启动时,一个执行main function的goroutine会被创建,称为main goroutinego func() ...

  5. golang中的race检测

    golang中的race检测 由于golang中的go是非常方便的,加上函数又非常容易隐藏go. 所以很多时候,当我们写出一个程序的时候,我们并不知道这个程序在并发情况下会不会出现什么问题. 所以在本 ...

  6. Golang控制goroutine的启动与关闭

    最近在用golang做项目的时候,使用到了goroutine.在golang中启动协程非常方便,只需要加一个go关键字: go myfunc(){ //do something }() 但是对于一些长 ...

  7. golang的goroutine与channel

    Golang的goroutine是非抢占式的, 令人相当蛋疼! 有痛不能呻吟...只能配合channel在各goroutine之间传递信号来实现抢占式, 而这形成了golang最灵活与最具性能的核心. ...

  8. [golang学习] goroutine调度

    这两天有些闲功夫, 学习下golang, 确实非常简洁. 不过有些缺憾. 在我的测试中. golang的调度(goroutine)似乎不是非常好. func say(k int) { fmt.Prin ...

  9. Golang中WaitGroup使用的一点坑

    Golang中WaitGroup使用的一点坑 Golang 中的 WaitGroup 一直是同步 goroutine 的推荐实践.自己用了两年多也没遇到过什么问题.直到一天午睡后,同事扔过来一段奇怪的 ...

随机推荐

  1. java获取mp3的时长和播放mp3文件

    所需包为jaudiotagger-2.2.6-SNAPSHOT.jar和jl1.0.1.jar. import java.io.BufferedInputStream; import java.io. ...

  2. Vue.js学习笔记(三) - 修饰符

    本篇将简单介绍常用的修饰符. 在上一篇中,介绍了 v-model 和 v-on 简单用法.除了常规用法,这些指令也支持特殊方式绑定方法,以修饰符的方式实现.通常都是在指令后面用小数点“.”连接修饰符名 ...

  3. Java代码优化六大原则

    单一职责 代码优化第一步,单一职责原则 (Single Responsibility Principle).对于一个Java类,应该仅有一个引起它变化的原因,也就是说,一个类中,应该是一组相关性很高的 ...

  4. 小程序开发教程:wx.setTopBarText(OBJECT)

    状态信息展示 当小程序被显示在聊天顶部时,开发者可将重要的状态变更信息实时展示出来,便于用户及时获知. 详见文档:小程序文档 小程序更新之后,有个更有用的功能!支持状态栏实时状态更新!! wx.set ...

  5. 解决无线网络连接出现黄色感叹号---win10

    今天使用公司的电脑,这个电脑是另一位同事用过的,然后到我这里就连不上网了.然后把自己解决的方法记录一下: 开始运行输入以下命令来重置IP. 打开运行输入:cmd 在命令窗口中输入:ipconfig / ...

  6. 设计模式--单例模式(Singleton pattern)及应用

    单例模式 参考文档: 该文仅介绍spring的单例模式:spring 的单例模式 介绍原理:Spring的单例模式底层实现 参考书籍:漫谈设计模式:从面向对象开始-刘济华.pdf 1. 单例模式解析 ...

  7. (转)Java.lang.reflect.Method invoke方法 实例

    背景:今天在项目中用到Method 的invoke方法,但是并不理解,查完才知道,原来如此! import java.lang.reflect.Method; /** * Java.lang.refl ...

  8. (转)生产者/消费者问题的多种Java实现方式 (待整理)

    实质上,很多后台服务程序并发控制的基本原理都可以归纳为生产者/消费者模式,而这是恰恰是在本科操作系统课堂上老师反复讲解,而我们却视而不见不以为然的.在博文<一种面向作业流(工作流)的轻量级可复用 ...

  9. 【javascript】ajax的参数

    1. 常用参数 url type: contentType: dataType: data: success: error 2. 特殊参数 context: 这个对象用于设置Ajax相关回调函数的上下 ...

  10. C++ STL Binary search详解

    一.解释 以前遇到二分的题目都是手动实现二分,不得不说错误比较多,关于返回值,关于区间的左闭右开等很容易出错,最近做题发现直接使用STL中的二分函数方便快捷还不会出错,不过对于没有接触过的同学,二分函 ...