为什么Go1.1从G-M模型转变成G-M-P模型?

翻译至:[Scalable Go Scheduler Design Doc]--DmitryVyukov (https://docs.google.com/document/d/1TTj4T2JO42uD5ID9e89oa0sLKhJYD0Y_kqxDv3I3XMw/edit#heading=h.mmq8lm48qfcw)

当前调度器的问题

  当前的goroutine调度器限制了用go编写编发程序的可伸缩性,特别是高吞吐量服务和并行计算程序。Vtocc (https://github.com/vitessio/vitess]服务在8核机子上的最大CPU消耗为70%,profile显示在runtime.futex()函数花费了14%。通常,调度器会禁止用户使用惯用的细颗粒度的并发,这对性能至关重要。

目前的实现存在以下问题:

  • 1.单个全局互斥锁(Schd.Lock)和集中的状态。此锁保护所有与goroutine有关的操作(创建,完成,重新调度等)

  • 2.Goroutine(G) 间的交替 (G.nextg)。工作线程(M)频繁地切换可运行的goroutine,这可能导致延迟增加和额外的开销。每个M必须能够执行任务可运行的G,特别是刚刚创建GM

    1. Per-M 内存缓存(M.mcache)。内存缓存与其他缓存(堆栈分配)都与所有M相关联,而其实它们只需要与运行Go代码的M相关联(在syscall内部阻塞的M其实并不需要mcache)。 运行Go代码的M与所有M的比率高达1:100。这导致过多的资源消耗(每个MCache最多可以到2M)和槽糕的数据局部性。
    1. 过于积极的线程阻塞/解除阻塞。在系统调度时,工作线程经常被阻塞和解除阻塞。这增加了很多开销。

设计

Processors

  普遍的想法是将P(Processors处理器)的概念引入运行时,并在处理器智之上实现work-stealing scheduler(工作窃取调度)http://supertech.csail.mit.edu/papers/steal.pdf程序

  M表示OS线程。P表示执行Go代码所需的资源。当M执行Go代码时,它有一个关联的P
M空闲或在系统调用时,它需要获取P

  我们拥有与GOMAXPROCS 相同数量的P。所有的P都被组织成一个数组,这是为了实现work-stealing工作窃取的要求。GOMAXPROCS 更改设计 stop/start the world 来重新调整P的数组。来自sched的一些变量被分散并移动到P,来自M的一些变量也被移动到P(与Go代码的主动执行相关的变量)

struct P
{
  Lock;
  G *gfree; // freelist, moved from sched
  G *ghead; // runnable, moved from sched
  G *gtail;
  MCache *mcache; // moved from M
  FixAlloc *stackalloc; // moved from M
  uint64 ncgocall;
  GCStats gcstats;
  // etc
...
};

P *allp; // [GOMAXPROCS]

还有一个无锁的空闲P列表:

P *idlep; // lock-free list

  当M开始执行Go代码时,必须先从列表中弹出P。当M结结束执行Go代码时,它将P塞回列表中。因此,当M执行Go代码时,它必须具有关联的P。这种机制渠道了sched.atomic(mcpu/mcpumax)

调度

  当创建新的GG变为可运行时,它被塞到当前P的可运行goroutine列表。当P完成执行G时,它首先尝试从自己的可运行goroutine列表中弹出G;如果列表为空,则P选择一个随机受害者(另一个P)并试图从中窃取一半可运行的goroutine

Syscalls/M 停止和非停止

  当M创建一个新的G时,它必须确保有另一个M来执行G(如果不是所有的M都处于忙碌)。类似的,当M进入系统调用时,它必须确保有另一个M来执行Go代码。
  有两个选项,我们可以迅速阻止和解锁M,或采用一些旋转。这是性能跟CPU不必要消耗之间的固有冲突。我们的想法是使用旋转并消耗CPU循环周期。但是,它不应该影响使用GOMAXPROCS = 1运行的程序(命令行实用程序,appengine等)。

  旋转分两个级别:(1)一个关联P的空闲M一直旋转寻找新的G; (2)一个关联P的w/o M旋转等待可用的P;最多有GOMAXPROCS数量的旋转M(包括(1)和(2))。当存在类型(2)的空闲M时,类型(1)的空闲M不会阻塞。

  当产生新的G,或者M进入系统调用,或者M从空闲转为忙时,它确保至少有1个旋转M(或者所有P都忙)。这确保了没有可以运行的可运行的G;并避免同时过多的M阻塞/解除阻塞。

  旋转主要是被动的(屈服于OS,sched_yield()),但可能包括一点点主旋(循环切换CPU)(需要调查和调整)。

终止/死锁检测

  终止/死锁检测在分布式系统中更存在问题。一般的想法是仅在所有P都空闲时才进行检查(空闲P的全局的原子计数器),这允做一些更昂贵代价的检查比如涉及 prep状态聚合的检查。

系统线程锁

  此功能不是性能关键。

    1. 锁定G变为不可运行(Gwaiting)。 M立即将P返回到空闲列表,唤醒另一个M并阻塞。
    1. 锁定G变为可运行(并到达runq的头部)。 当前M移出自己的P并将G锁定到与锁定的G相关联的M,并解锁它。 当前的M变得空闲。
实施

目标是将整个事物分成可以独立审查和提交的最小部分。

  • 1.介绍P结构; 实现allp / idlep容器(idlep为启动器提供互斥保护); 将P与M运行Go代码相关联。 全局互斥和原子状态仍然存在。
  • 2.将G freelist移动到P.
  • 3.将mcache移动到P.
  • 4.将stackalloc移动到P.
  • 5.将ncgocall / gcstats移动到P.
  • 6.分散运行队列,实现工作窃取。 消除G的不可接触。 这部分操作仍在全局互斥下。
  • 7.删除全局互斥锁,实现分布式终止检测,LockOSThread。
  • 8.实现旋转而不是提示阻止/解除阻塞。

该计划可能会失效,有很多未探索的细节。

潜在的进一步改进
  • 1.尝试LIFO调度,局部上有所提升。 但是,它仍然必须提供一定程度的公平性,并优雅地处理屈服的goroutines。
  • 2.在goroutine首次运行之前,不要分配G和堆栈。 对于新创建的goroutine,我们只需要callerpc,fn,narg,nret和args,即大约6个单词。 这将允许创建大量运行到完成的goroutine,显着降低内存开销。
  • 4.更好的G-to-P局部性。 尝试将未阻塞的G排入上一次运行的P。
    1. P-to-M的更好的局部性。 尝试在上次运行的同一个M上执行P.
  • 6.限制M创建。 调度程序可以很容易地强制每秒创建数千M,直到OS拒绝创建更多线程。 必须立即创建M,直到k * GOMAXPROCS,之后可以通过计时器添加新的M.
其他
  • 由于这项工作,GOMAXPROCS不会消失。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容

  • 阅读Go并发编程对go语言线程模型的笔记,解释的非常到,好记性不如烂笔头,忘记的时候回来翻一番,在此做下笔记。 G...
    WithLin阅读 1,139评论 0 4
  • 从源码角度看Golang的调度 本章主要从源码角度针对Go调度相关进行分析。仅关注linux系统下的逻辑。代码版本...
    thinkboy234阅读 1,294评论 0 6
  • 我们的程序是如何被运行的? 学习过操作系统的人,应该对进程和线程的模型都是有所了解的。按照我的理解:「进程」是操作...
    蔡欣圻阅读 2,853评论 1 7
  • 前言 随着服务器硬件迭代升级,配置也越来越高。为充分利用服务器资源,并发编程也变的越来越重要。在开始之前,需要了解...
    云爬虫技术研究笔记阅读 3,828评论 0 7
  • 没想到2018年的首篇今天才开始动笔。似乎每年的结束和伊始都要感慨一下时间流逝的太快,而自己做的又太少。如今越来越...
    冰糖糖冰阅读 304评论 0 0