GMP dispatch model

Development of GMP: Before version go 1.1, the GM model + global queue model was used.

GM model + global queue mode

M: 1 = kernel thread: coroutine

When a new coroutine G is created, it will be put into the global queue. Every time a coroutine G is executed, the kernel thread M will obtain a coroutine G from the global queue for execution. Because there are multiple kernel threads M, there are concurrency issues. , so every time coroutine G is fetched from the queue, it must be locked, so there will be performance problems when concurrency is high.

Solution: Assign a coroutine queue to the kernel thread

GMP model + global queue

M:N:=kernel thread:coroutine

The number of p corresponds to the number of cores set.

GMP model :

G -> goroutine (coroutine)

P -> Processor scheduler (if the thread wants to run goroutine, it must first obtain P. P also contains the runnable G queue) (note that it is not cpu)

M -> thread kernel thread (each M represents a kernel thread, and the OS scheduler is responsible for allocating kernel threads to the CPU core for execution.)

Model introduction

Scheduler design strategy

Parallelism (multi-core and multi-threading to achieve parallelism), preemption (limit the binding time of G and M, up to 10ms, unbind after the time to avoid starvation of other coroutines), reuse threads (stealing + separation), reduce global competition

There are multiple PMs to execute multiple G;p local queue up to 256g

A new coroutine G will be put into the queue of the local team first. If the queue P of the local team is full, G will be put into the global queue. When the local queue is empty, it will be obtained from the global queue. If the global queue If the queue is empty, the coroutine will be taken from other teams to its own local queue. If Ctrip is blocked midway, the local queue will run on other kernel threads.

Note: The number of M has nothing to do with the number of P. If the current M is blocked, P's goroutine will run on other M, or create a new M. So there may be many M's and only one P.

What process does "go func()" go through?

Scheduler life cycle

GO life cycle: creation, saving, acquisition, scheduling execution, blocking, destruction

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

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

Guess you like

Origin blog.csdn.net/xia_2017/article/details/128821179