GMPディスパッチモデル

GMP の開発: バージョン 1.1 より前は、GM モデル + グローバル キュー モデルが使用されていました。

GMモデル+グローバルキューモード

M: 1 = カーネル スレッド: コルーチン

新しいコルーチン G が作成されると、それはグローバル キューに置かれます。コルーチン G が実行されるたびに、カーネル スレッド M は実行のためにグローバル キューからコルーチン G を取得します。カーネル スレッド M は複数あるため、したがって、コルーチン G がキューからフェッチされるたびにロックする必要があるため、同時実行性が高い場合にはパフォーマンスの問題が発生します。

解決策: コルーチン キューをカーネル スレッドに割り当てます。

GMPモデル+グローバルキュー

M:N:=カーネルスレッド:コルーチン

p の数は、設定されているコアの数に対応します。

GMPモデル

G -> ゴルーチン (コルーチン)

P -> プロセッサスケジューラ (スレッドがゴルーチンを実行したい場合は、最初に P を取得する必要があります。P には実行可能な G キューも含まれています) (CPU ではないことに注意してください)

M -> スレッド カーネル スレッド (各 M はカーネル スレッドを表し、OS スケジューラは、実行のためにカーネル スレッドを CPU コアに割り当てる責任を負います)。

モデル紹介

スケジューラーの設計戦略

並列処理 (並列処理を実現するためのマルチコアおよびマルチスレッド)、プリエンプション (G と M のバインド時間を最大 10 ミリ秒に制限し、他のコルーチンの枯渇を避けるために時間が経過するとバインドを解除します)、スレッドの再利用 (スチール + 分離)、削減世界的な競争

最大 256g までの複数の G;P ローカル キューを実行する複数の PM があります

新しいコルーチン G は、最初にローカル チームのキューに入れられます。ローカル チームのキュー P がいっぱいの場合、G はグローバル キューに入れられます。ローカル キューが空の場合は、グローバル キューから取得されます。グローバル キューの場合 キューが空の場合、コルーチンは他のチームから独自のローカル キューに移されます。Ctrip が途中でブロックされた場合、ローカル キューは他のカーネル スレッドで実行されます。

注: M の数は P の数とは関係ありません。現在の M がブロックされている場合、P のゴルーチンは他の M 上で実行されるか、新しい M を作成します。つまり、M はたくさんいて、P は 1 つだけである可能性があります。

「go func()」はどのような処理を行うのでしょうか?

スケジューラのライフサイクル

GO ライフサイクル: 作成、保存、取得、実行のスケジュール設定、ブロック、破棄

CPU感知不到协程,GO调度器把协程调度到内核线程上去,然后操作系统调度器将内核线程放到cpu上执行;m是内核线程的封装。go调度器工作就是将G分配到M

M的几种状态:休眠(未绑定P)、运行、自旋(绑定了p 只是没有可执行G)。

运行+自旋<= gomaxproc数量

需要注意的点:

M和P不是绝对的1:1 ,有G阻塞的时候,M也会阻塞,会有新的M来继续执行P队中的G

p队列是先进先出

work stealing时,从偷取的P那里,取尾部的一半放到自己的P中

从全局队列中取时,取n个,len(GQ)为全局队列G的个数,公式: n = min(len(GQ)/GOMAXPROCS + 1, len(GQ/2))

本地队列满了,又创建新的G,此时把本地队列的前一半与新创建的G,打乱后,一起放到全局队列中

自旋线程的最大限制不能超过GOMAXPROCS,多出的线程休眠(⻓时间休眠等待GC回收销毁)

原文链接:https://blog.csdn.net/qq_42956653/article/details/121234816

扩展

  1. 如何控制同一时间的并发执行数,不能让程序崩溃?

(1)使用sync.WaitGroup控制协程数量。不是动态控制法,只是控制并发了

(2)使用sync.Mutex控制协程数量 ,这个待验证

(3) 使用带缓冲的通道限制并发数 。创建chan的时候手动设置缓存: c:=make(chan int, 2), 在协程创建前写入chan在协程结束前读出chan,达到限制并发数的需求

有关M和P的个数问题

  1. P的数量:

  • 由启动时环境变量 $GOMAXPROCS 或者是由 runtime 的方法 GOMAXPROCS() 决定。这意味着在程序执行的任意时刻都只有 $GOMAXPROCS 个 goroutine 在同时运行。

  1. M的数量:

  • go 语言本身的限制:go 程序启动时,会设置 M 的最大数量,默认 10000. 但是内核很难支持这么多的线程数,所以这个限制可以忽略。

  • runtime/debug 中的 SetMaxThreads 函数,设置 M 的最大数量

  • 一个 M 阻塞了,会创建新的 M。

M 与 P 的数量没有绝对关系,一个 M 阻塞,P 就会去创建或者切换另一个 M,所以,即使 P 的默认数量是 1,也有可能会创建很多个 M 出来。

P 和 M 何时会被创建?

  1. 在确定了 P 的最大数量 n 后,运行时系统会根据这个数量创建 n 个 P。

  1. 没有足够的 M 来关联 P 并运行其中的可运行的 G。比如所有的 M 此时都阻塞住了,而 P 中还有很多就绪任务,就会去寻找空闲的 M,而没有空闲的,就会去创建新的 M。

调度器的设计策略?

复用线程:避免频繁的创建、销毁线程,而是对线程的复用。

  1. work stealing 机制

  • 当本线程无可运行的 G 时,尝试从其他线程绑定的 P 偷取 G,而不是销毁线程。

  1. hand off 机制

  • 当本线程因为 G 进行系统调用阻塞时,线程释放绑定的 P,把 P 转移给其他空闲的线程执行。

利用并行:GOMAXPROCS 设置 P 的数量,最多有 GOMAXPROCS 个线程分布在多个 CPU 上同时运行。GOMAXPROCS 也限制了并发的程度,比如 GOMAXPROCS = 核数/2,则最多利用了一半的 CPU 核进行并行。

抢占:在 coroutine 中要等待一个协程主动让出 CPU 才执行下一个协程,在 Go 中,一个 goroutine 最多占用 CPU 10ms,防止其他 goroutine 被饿死,这就是 goroutine 不同于 coroutine 的一个地方。

全局 G 队列:,当 M 执行 work stealing 从其他 P 偷不到 G 时,它可以从全局 G 队列获取 G。

调度器的生命周期?

M0

M0 是启动程序后的编号为 0 的主线程,这个 M 对应的实例会在全局变量 runtime.m0 中,不需要在 heap 上分配,M0 负责执行初始化操作和启动第一个 G, 在之后 M0 就和其他的 M 一样了。

G0

G0 是每次启动一个 M 都会第一个创建的 gourtine,G0 仅用于负责调度的 G,G0 不指向任何可执行的函数,每个 M 都会有一个自己的 G0。在调度或系统调用时会使用 G0 的栈空间,全局变量的 G0 是 M0 的 G0。

可视化 GMP 编程?

方式 1:go tool trace 方式 2:Debug trace

更多详情点击查看点击这里

おすすめ

転載: blog.csdn.net/xia_2017/article/details/128821179