一、Go 的协程 goroutine

go 的特性:协程(goroutine),goroutine 是 go 自己实现的、为了解决线程的性能问题,goroutine 协程是用户态的,由 go runtime 创建和销毁,没有内核消耗,线程是内核态的,与操作系统相关,创建和销毁成本较高。

goroutine 提高 cpu 的利用率,解决了高消耗的CPU调度,用户态的轻量级的线程,约4k

这也是 go 为什么性能那么好的原因,而 go 实现 goroutine 协程的原理:GMP 调度模型

二、GMP 调度模型

  • G:

    goroutine 协程,go runtime 包自己实现的一种数据结构,存储执行时唯一的内存栈信息

  • P:

    processor 处理器,go runtime 包实现的调度器,主要用来并发调度 goroutine 协程的启动、执行、等待、暂停、销毁等生命周期

  • M:

    thread 线程,就是我们平时理解的线程,如果你不理解什么是线程,请参考文章 进程线程协程的概念和区别

那么 go 的协程 goroutine 是如何实现以及调度执行的过程是什么样子的呢?

比如我们使用 go 开启一个协程:

go func(){
// 开启协程,处理逻辑
}()

'go func()'经历了哪些过程

    1. 通过 'go func()' 创建一个 goroutine(数据结构,有个唯一 gid,以及内存栈信息),这里称为:G
    1. 有两个存储 G 的队列(本地P队列,全局队列),新创建的 G 会优先加入本地 P 队列中,如果满了就会保存在全局队列中
    1. G 最终会通过 P 调度运行在 M 中,MP 是组合(一个M必须持有一个P,M:P=1:1)
    1. M 会从 P 的队列中弹出一个可执行的 G 来执行,如果没有,则会从全局队列中获取,

      全局队列也没有,则会从其他 MP 队列中偷取一个 G执行,从其他 P 偷的方式称为 work stealing

    1. 一个 M 调度 G 执行是一个循环过程
    1. 当 M 执行 G 过程中发生 systemCall 阻塞,M 会阻塞,如果当前有一些 G 在执行,runtime 会把这个线程 M 从 P 中摘除(detach),此时 P 会和 M 解绑即 hand off,然后再创建/从休眠队列中取一个 M 来服务这个 P
    • 系统调用(如文件IO)阻塞(同步):阻塞MG,M与P解绑

    • 网络 IO 调用阻塞(异步):G 移动到NetPoller,M 继续执行 P 中的 G

    • mutex/chan阻塞(异步):G 移动到 chan 的等待队列中,M 继续执行 P 中的 G

    1. 当 M 系统调用结束后进入休眠/销毁状态,这个 G 会尝试获取一个空闲的 P 执行,如果没有,这个 G 会放入全局队列

M 每隔约 10ms 会切换一个 G,被切换的 G 会重新回到本地P队列

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

三、M0 & G0 的启动

go 启动的时候,默认会启动 M0 线程 和 G0 协程

M0:编号为 0 的主线程

GO:编号为 0 的主协程

四、 协程 goroutine 的调度策略

  • 队列轮转:

    P 会周期性的将G调度到M中执行,执行一段时间后,保存上下文,将 G 放到队列尾部,然后从队列中再取出一个G进行调度

    除此之外,P还会周期性的查看全局队列是否有G等待调度到M中执行

  • 系统调度:

    当 G0 即将进入系统调用时,M0 将释放 P,进而某个空闲的 M1 获取 P,继续执行 P 队列中剩下的 G。

    M1 的来源有可能是 M 的缓存池,也可能是新建的

    当 G0 系统调用结束后,如果有空闲的P,则获取一个P,继续执行 G0。如果没有,则将 G0 放入全局队列,等待被其他的 P 调度。然后 M0 将进入缓存池睡眠

  • 抢占式调度:

    sysmon 监控协程,如果 g 运行时间过长 10 ms,那会发送信号给到 m,g 会被挂起,m继续执行 p 中的 g,防止其他 g 被饿死

五、协程的生命周期

创建、等待(调用 gopark 进入等待状态)、唤醒执行(调用 goready 唤醒等待的 g 执行)、销毁

五、GMP 的数量

G 的初始化大小是 2-4 k,具体数量由内存决定,

P 的数量由用户设置的 GoMAXPROCS 决定,等于CPU的核心数,但是不论 GoMAXPROCS 设置为多大,P 的储存G的数量最大为 256

M 默认限制 10000

常见问题

1. Golang 为什么要创建 goroutine 协程

轻量:1.大小只有 2-4 k,用户级线程,减少了内核态切换创建的开销

操作系统中虽然已经有了多线程、多进程来解决高并发的问题,但是在当今互联网海量高并发场景下,对性能的要求也越来越苛刻,大量的进程/线程会出现内存占用高、CPU消耗多的问题,很多服务的改造与重构也是为了降本增效。

一个进程可以关联多个线程,线程之间会共享进程的一些资源,比如内存地址空间、打开的文件、进程基础信息等,每个线程也都会有自己的栈以及寄存器信息等,线程相比进程更加轻量,而协程相对线程更加轻量,多个协程会关联到一个线程,协程之间会共享线程的一些信息,每个协程也会有自己的栈空间,所以也会更加轻量级。从进程到线程再到协程,其实是一个不断共享,减少切换成本的过程。

Golang 使用协程主要有以下几个原因:

● 大小

协程大概是2-4k,线程大概是1m

● 创建、切换和销毁

协程是用户态的,由 runtime 创建和销毁,没有内核消耗,线程是内核态的,与操作系统相关,创建和销毁成本较高

2. 什么是 CSP 并发模型?

CSP 并发模型:不要以共享内存的方式来通信,而以通信的方式来共享内存

go 实现 CSP 并发模式是通过: goroutine + chan

3. G 调度执行中断是如何恢复的?

G 是一个数据结构,存储上下文堆栈信息

中断的时候将寄存器里的栈信息,保存到自己的 G 对象(sudog)里面。当再次轮到自己执行时,将自己保存的栈信息复制到寄存器里面,这样就接着上次之后运行了。

4. 当 G 阻塞时,g、m、p 会发生什么

G 的状态会从运行态变为阻塞态,放入 P 等待队列

M 会从该 Goroutine 所在的 P 中分离出来,转而执行其他 Goroutine

P 会从该 Goroutine 所在的 M 中分离出来,将该 Goroutine 放入等待队列中,并从空闲的 M 队列中取出一个 M,将其绑定到该 P 上

5. runtime 是什么?

golang 底层的基础设施:

  • GPM 的创建和调度

  • 内存分配

  • GC

  • 内置函数如 map,chan,slice,反射的实现等

  • pprof,trace,CGO

  • 操作系统以及 CPU 的一些封装

  • ....

基本上就是 go 的底层所在了

6. 怎么启动第一个 goroutine?

main 启动函数会默认启动 G0 协程

7. Go 的协程为什么那么好,与线程的区别

● 大小

协程大概是 2k-4k,线程大概是1m

● 创建、切换和销毁

协程是用户态的,由runtime创建和销毁,没有内核消耗,线程是内核态的,与操作系统相关,创建和销毁成本较高

提高cpu的利用率,解决了高消耗的CPU调度,用户态的轻量级的线程,约4k

减少了内核切换成本,操作系统分为用户态和内核态(表示操作系统底层)

8. 线程与协程的区别

一个线程有多个协程,协程是用户态的轻量级的线程,非抢占式的,由用户控制,没有内核切换的开销

原文地址

Go 原理之 GMP 并发调度模型

Go 原理之 GMP 并发调度模型的更多相关文章

  1. Goroutine并发调度模型深度解析之手撸一个协程池

    golanggoroutine协程池Groutine Pool高并发 并发(并行),一直以来都是一个编程语言里的核心主题之一,也是被开发者关注最多的话题:Go语言作为一个出道以来就自带 『高并发』光环 ...

  2. go并发调度原理学习

    aaarticlea/jpeg;base64,/9j/4AAQSkZJRgABAQAAkACQAAD/4QB0RXhpZgAATU0AKgAAAAgABAEaAAUAAAABAAAAPgEbAAUAA

  3. 图解协程调度模型-GMP模型

    现在无论是客户端.服务端或web开发都会涉及到多线程的概念.那么大家也知道,线程是操作系统能够进行运算调度的最小单位,同一个进程中的多个线程都共享这个进程的全部系统资源. 线程 三个基本概念 内核线程 ...

  4. 重新梳理调度器——GMP 调度模型

    调度器--GMP 调度模型 Goroutine 调度器,它是负责在工作线程上分发准备运行的 goroutines. 首先在讲 GMP 调度模型之前,我们先了解为什么会有这个模型,之前的调度模型是什么样 ...

  5. [golang]Golang实现高并发的调度模型---MPG模式

    Golang实现高并发的调度模型---MPG模式 传统的并发形式:多线程共享内存,这也是Java.C#或者C++等语言中的多线程开发的常规方法,其实golang语言也支持这种传统模式,另外一种是Go语 ...

  6. C++11并发内存模型学习

    C++11标准已发布多年,编译器支持也逐渐完善,例如ms平台上从vc2008 tr1到vc2013.新标准对C++改进体现在三方面:1.语言特性(auto,右值,lambda,foreach):2.标 ...

  7. OS中处理机调度模型和调度算法

    OS中处理机调度模型和调度算法 调度层次 1.1. 高级调度(长程调度,作业调度) 功能:依据某种算法.把在外存队列上处于后备队列的那些作业调入内存.以作业为操做对象. 作业:比程序更为广泛的概念,不 ...

  8. 4、Java并发性和多线程-并发编程模型

    以下内容转自http://ifeve.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E6%A8%A1%E5%9E%8B/: 并发系统可以采用多种并发编程模型来实现. ...

  9. word2vec原理(一) CBOW与Skip-Gram模型基础

    word2vec原理(一) CBOW与Skip-Gram模型基础 word2vec原理(二) 基于Hierarchical Softmax的模型 word2vec原理(三) 基于Negative Sa ...

  10. Java并发编程的艺术读书笔记(2)-并发编程模型

    title: Java并发编程的艺术读书笔记(2)-并发编程模型 date: 2017-05-05 23:37:20 tags: ['多线程','并发'] categories: 读书笔记 --- 1 ...

随机推荐

  1. Vite 3 来了!新增功能 + 如何迁移

    @charset "UTF-8"; .markdown-body { line-height: 1.75; font-weight: 400; font-size: 15px; o ...

  2. C#之清除已经注册的事件

    private static void DealA(object sender, UnhandledExceptionEventArgs e) { Console.WriteLine($"E ...

  3. 详解Git中的.gitignore文件

    1.什么是.gitignore文件?有什么作用? 在Git中,有一种特殊的文件,其文件全名就是 .gitignore,这个文件可以用txt打开,主要功能是屏蔽某些文件,使得这些文件不被追踪(track ...

  4. 5 easybr指纹浏览器内存修改教程

    目的 navigator.deviceMemory可以暴露设备的物理内存和运行状态,被用于设备唯一性识别或判断设备等级. 通过伪造这类信息,可以增强防关联.防追踪能力. easybr指纹浏览器提供演示 ...

  5. css——慕课

  6. 玩转代码:深入GitHub,高效管理我们的“shou学”平台源代码

    玩转代码:深入GitHub,高效管理我们的"shou学"平台源代码 在当今快节奏的开发世界中,有效地管理代码不仅仅是一种良好实践,更是一种必需.无论您是独立开发者还是大型团队的一员 ...

  7. React Native开发鸿蒙Next---RN键盘问题

    .markdown-body { line-height: 1.75; font-weight: 400; font-size: 16px; overflow-x: hidden; color: rg ...

  8. pytorch入门 - 微调huggingface大模型

    在自然语言处理(NLP)领域,预训练语言模型如BERT已经成为主流.HuggingFace提供的Transformers库让我们能够方便地使用这些强大的模型. 本文将详细介绍如何使用PyTorch微调 ...

  9. .NET 10 支持Linux 的Shebang(Hashbang)

    .NET 10 Preview 5 带来的C# 文件脚本化运行,在 Linux/Unix 系统中通过 #!/usr/bin/dotnet run 支持 Shebang(Hashbang) 的详细说明: ...

  10. 最值得关注的2025年公众号编辑器Top 10,提升你的内容质量

    公众号编辑器全面指南:10款主流工具深度评测 前言 公众号编辑器作为新媒体和自媒体人日常必备的工具,能极大提升公众号运营效率和内容质量.面对众多类型和风格各异的公众号编辑器网站,如何选择一款真正适合自 ...