go为什么这么快?(再探GMP模型)

进程、线程、协程

进程:进程是系统进行资源分配的基本单位,有独立的内存空间,单切换代价极高,进程间通信也比较麻烦

线程:线程是CPU调度和分派的基本单位,线程依附于进程,与其他线程共享进程的资源,仅有自己的(程序计数器,一组寄存器的值,和栈),线程切换代价小(但是线程之间的切换可能会设计用户态和内核态的切换),由于共享进程资源,所以线程之间通信比较方便。

协程协程是一种用户态的轻量级线程,协程的调度完全由用户控制,协程切换只需要保存和恢复任务的上下文,没有内核的开销。协程间通信也比较简单(协程间本身是不可抢占的,由于操作系统的调度机制无法影响到它,因此一般存在用户自定义的调度机制)(也可以这么说内核线程依然叫“线程(thread)”,用户线程叫“协程(co-routine)".)

Golang为并发而生

Goroutine非常轻量,主要体现在以下方面:

  1. 上下文切换代价小,Goroutine的上下文切换只涉及到三个寄存器(PC/SP/DX)的值的修改,而线程的切换需要涉及模式转换,以及16个寄存器的刷新。
  2. 内存占用少,线程栈空间一般是2M,而goroutine只需要2k;

Go的调度器实现机制

Go程序通过调度器来调度Goroutine在内核级线程上执行,但是并不直接绑定os线程M-Machine运行,而是由Goroutine Scheduler中的 P-processor作获取内核线程资源的【中介】

Go的调度器通常被称为G-M-P模型,实际包含四个结构,分别为:
G:Goroutine,每个Gotoutine对应一个G结构体,G存储Goroutine的运行堆栈,状态,以及任务函数,可重用(函数实体)G需要保存到P才能被调度执行

M:machine,os内核线程抽象,代表真正执行计算的资源,在绑定有效的P后,进入schedule循环;而shcedule循环的机制大致是从Global队列,P的local队列以及wait队列中获取。

M的数量是不固定的,有Go Runtime调整,为了防止创建过多OS线程导致系统调度不过来,目前默认设置为10000个,M不保存G的上下文,这是G可以跨M的基础。

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

Sche:Go调度器,它维护有存储M和G的队列以及调度器的一些状态信息等。

调度器循环的机制大致是从各种队列、P 的本地队列中获取 G,切换到 G 的执行栈上并执行 G 的函数,调用 Goexit 做清理工作并回到 M,如此反复。

可以通过经典的地鼠推车搬砖的模型来说明其三者关系:

image

地鼠(Gopher)的工作任务是:工地上有若干砖头,地鼠借助小车把砖头运送到火种上去烧制。M 就可以看作图中的地鼠,P 就是小车,G 就是小车里装的砖。

Processor(P)
根据用户设置的 GoMAXPROCS 值来创建一批小车(P)。

Goroutine(G)
通过 Go 关键字就是用来创建一个 Goroutine,也就相当于制造一块砖(G),然后将这块砖(G)放入当前这辆小车(P)中。

Machine (M)
地鼠(M)不能通过外部创建出来,只能砖(G)太多了,地鼠(M)又太少了,实在忙不过来,刚好还有空闲的小车(P)没有使用,那就从别处再借些地鼠(M)过来直到把小车(P)用完为止。

这里有一个地鼠(M)不够用,从别处借地鼠(M)的过程,这个过程就是创建一个内核线程(M)

需要注意的是:地鼠(M) 如果没有小车(P)是没办法运砖的,小车(P)的数量决定了能够干活的地鼠(M)数量,在 Go 程序里面对应的是活动线程数;

在Go程序里,我们也可以通过下面的图示来展示G-M-P模型。

image

Go 调度器中有两个不同的运行队列:全局运行队列(GRQ)和本地运行队列(LRQ)。

每个 P 都有一个 LRQ,用于管理分配给在 P 的上下文中执行的 Goroutines,这些 Goroutine 轮流被和 P 绑定的 M 进行上下文切换。GRQ 适用于尚未分配给 P 的 Goroutines。

从上图可以看出,G 的数量可以远远大于 M 的数量,换句话说,Go 程序可以利用少量的内核级线程来支撑大量 Goroutine 的并发M:N模型。多个 Goroutine 通过用户级别的上下文切换来共享内核线程 M 的计算资源,但对于操作系统来说并没有线程上下文切换产生的性能损耗

为了更加充分利用线程的计算资源,Go 调度器采取了以下几种调度策略

任务窃取
为了提高 Go 并行处理能力,调高整体处理效率,当每个 P 之间的 G 任务不均衡时,调度器允许从 GRQ,或者其他 P 的 LRQ 中获取 G 执行。

减少阻塞
在Go里阻塞主要分为以下4个场景:

1.由于原子、互斥量或channel操作调用导致阻塞,调度器将把当前阻塞的 Goroutine 切换出去,重新调度 LRQ 上的其他 Goroutine。

2.由于网络请求和 IO 操作导致 Goroutine 阻塞
Go 程序提供了网络轮询器(NetPoller)来处理网络请求和 IO 操作的问题,其后台通过 kqueue(MacOS),epoll(Linux)或 iocp(Windows)来实现 IO 多路复用。

通过使用 NetPoller 进行网络系统调用,调度器可以防止 Goroutine 在进行这些系统调用时阻塞 M。这可以让 M 执行 P 的 LRQ 中其他的 Goroutines,而不需要创建新的 M。有助于减少操作系统上的调度负载。

G1 正在 M 上执行,还有 3 个 Goroutine 在 LRQ 上等待执行。网络轮询器空闲着,什么都没干。

image

接下来,G1 想要进行网络系统调用,因此它被移动到网络轮询器并且处理异步网络系统调用。然后,M 可以从 LRQ 执行另外的 Goroutine。此时,G2 就被上下文切换到 M 上了。

image

最后,异步网络系统调用由网络轮询器完成,G1 被移回到 P 的 LRQ 中。一旦 G1 可以在 M 上进行上下文切换,它负责的 Go 相关代码就可以再次执行。这里的最大优势是,执行网络系统调用不需要额外的 M。网络轮询器使用系统线程,它时刻处理一个有效的事件循环。

image

3.当调用一些系统方法的时候,如果系统方法调用的时候发生阻塞,这种情况下,网络轮询器(NetPoller)无法使用,而进行系统调用的 Goroutine 将阻塞当前 M。

让我们来看看同步系统调用(如文件 I/O)会导致 M 阻塞的情况:G1 将进行同步系统调用以阻塞 M1。

image

调度器介入后:识别出 G1 已导致 M1 阻塞,此时,调度器将 M1 与 P 分离,同时也将 G1 带走。然后调度器引入新的 M2 来服务 P。此时,可以从 LRQ 中选择 G2 并在 M2 上进行上下文切换。

image

阻塞的系统调用完成后:G1 可以移回 LRQ 并再次由 P 执行。如果这种情况再次发生,M1 将被放在旁边以备将来重复使用。

image

4.在Goroutine中去执行一个sleep操作,导致M被阻塞
Go 程序后台有一个监控线程 sysmon,它监控那些长时间运行的 G 任务然后设置可以强占的标识符,别的 Goroutine 就可以抢先进来执行。

只要下次这个 Goroutine 进行函数调用,那么就会被强占,同时也会保护现场,然后重新放入 P 的本地队列里面等待下次执行。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容