Goroutine原理介绍

为什么Golang需要单独开发一个Goroutine?

  • 开销问题:

    POSIX的thread API虽然能够提供丰富的API,例如配置自己的CPU亲和性,申请资源等等,线程在得到了很多与进程相同的控制权的同时,开销也非常的大,在Goroutine中则不需这些额外的开销,所以一个Golang的程序中可以支持10w级别的Goroutine。

  • 调度性能:

    在Golang的程序中,操作系统级别的线程调度,通常不会做出合适的调度决策。例如在GC时,内存必须要达到一个一致的状态。在Goroutine机制里,Golang可以控制Goroutine的调度,从而在一个合适的时间进行GC。

Goroutine的实现原理

  • 两种备选方案

    • (M:1)多个用户态的线程对应一个系统线程,它可以做快速的上下文切换。缺点是不能有效利用多核CPU
    • (1:1)一个用户态的线程对应一个系统线程,它可以利用多核机制,但上下文切换需要消耗额外的资源
  • Golang的做法

    • (M:N)Golang采取了一种多对多的方案。M个用户线程对应N个系统线程,缺点增加了调度器的实现难度
  • 角色:

    • M: 代表了系统线程,由操作系统管理

    • G:Goroutine的实体,包括了调用栈,重要的调度信息,例如channel等。

    • P:衔接M和G的调度上下文,它负责将等待执行的G与M对接。

      P的数量由环境变量中的GOMAXPROCS决定,通常来说它是和核心数对应,例如在4Core的服务器上回启动4个线程。G会有很多个,每个P会将Goroutine从一个就绪的队列中做Pop操作,为了减小锁的竞争,通常情况下每个P会负责一个队列。

  • 挂起

    在Goroutine需要执行一个系统调用时,由于M是一个线程,所以必须等待它执行完才能执行其他的Goroutine。当一个新的Goroutine产生,M需要保证会有另外的一个M能够执行这个G,简单来说,当一个M进行系统调用,需要保证有另外的一个M能够继续执行Go代码。

    • 何时恢复?

      当系统调用返回时,M需要找到一个对应的P,以便能够运行Goroutine,它首先会尝试从其他线程中窃取一个P,如果不成功,它会将Goroutine放在一个全局的队列中,并将自己放在thread cache中。

    • 如何窃取

      这里有篇paper来描述这个设计:work-steal.

      简单来说,当队列不平衡时,会从其他队列中截取一部分Goroutine到P上进行调度。

参考链接

https://docs.google.com/document/d/1TTj4T2JO42uD5ID9e89oa0sLKhJYD0Y_kqxDv3I3XMw/edit#
https://morsmachine.dk/go-scheduler
(翻译)[https://www.zhihu.com/question/20862617]

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • http://skoo.me/go/2013/11/29/golang-schedule?hmsr=studygo...
    baboon阅读 2,273评论 0 3
  • Goroutine是Go里的一种轻量级线程——协程。相对线程,协程的优势就在于它非常轻量级,进行上下文切换的代价非...
    witchiman阅读 4,857评论 0 9
  • 操作系统的调度模型是大致上有两种N:1和1:1. N:1模型中用户态的线程运行在一个内核线程上,这种方式上下文切换...
    ieasy_tm阅读 747评论 0 4
  • 概要 本文从几个角度入手,描述和学习调度器原理 讲解调度器的基本概念 go语言的作者实现的C的协程库 libtas...
    zengfan阅读 6,422评论 0 21
  • 轻量级进程模型: 用同步IO的方法写程序的逻辑,第二点是用尽可能多的并发进程来提升IO并发的能力。 核心思想,第...
    lifesoul阅读 2,714评论 4 1