Golang 的 GMP:并发编程的艺术

这篇具有很好参考价值的文章主要介绍了Golang 的 GMP:并发编程的艺术。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

在 Golang 的并发编程中,GMP 是一个重要的概念,它代表了 Goroutine、M(线程)和 P(调度器)。这个强大的三位一体的并发模型使得 Golang 在处理并发任务时非常高效和灵活。通过 GMP 的组合,Golang 实现了一种高效的并发模型。它充分利用了多核处理器的优势,并通过轻量级的 Goroutine 实现了高并发的编程模式。但是GPM到底是怎么工作的呢?今天这篇文章就为您解开GPM的神秘面纱。

调度器的由来

单进程系统

Golang 的 GMP:并发编程的艺术,golang,网络,服务器

早期的计算机都是单进程操作系统,各个进程之间都是顺序执行,也就是进程A执行完了才能执行进程B。

「对于cpu来说,进程和线程是一样的,这里我们就不讨论进程和线程的区别了」。

存在的问题
  • 单一执行流程,计算机只能一个任务一个任务的处理。
  • 如果进程A阻塞,会带来很多cpu浪费的时间。

多进程/线程操作系统

基于以上的问题,于是就出现了多进程/线程操作系统。

Golang 的 GMP:并发编程的艺术,golang,网络,服务器

  • 系统把cpu分成了一段一段的时间片(微妙级别)。
  • cpu在第一个时间片执行进程A,然后切换到进程B执行,再切换到进程C,一直这样轮询的执行。
  • 因为cpu被分成的时间片是微妙级别的,所以直观的感觉就是进程A,B,C是在同时执行的。
  • 多进程/线程操作系统的确解决了阻塞的问题,但是又出现了新的问题。
存在的问题

Golang 的 GMP:并发编程的艺术,golang,网络,服务器

  • 因为cpu需要不断地进程A,B,C之间切换,切换肯定避免不了各种复制,计算等消耗,所以在切换过程中浪费掉了很多时间成本,所以「进程/线程越多」,切换「成本就越大」,也就越「浪费」。
  • 在这种模式下运行CPU在切换动作上浪费的时间成本大概是40%,只有60%的时间是在执行程序。
  • 进程和线程对内存的占用是比较大的,在32位的操作系统中,进程占用的虚拟内存大概是4GB,现成占用内存大概是4M。

Golang 的 GMP:并发编程的艺术,golang,网络,服务器

协程的诞生

对于一个线程来说其实分为两部分,「用户空间」和「内核空间」。

Golang 的 GMP:并发编程的艺术,golang,网络,服务器

Golang 的 GMP:并发编程的艺术,golang,网络,服务器

  • 内核空间主要是指操作系统底层,包括进程开辟,分配物理内存资源,磁盘资源等。
  • 用户空间主要是编码业务逻辑部分。
  • 于是有人想到能不能把线程的内核空间和用户空间分开。并且让他们互相绑定在一起。
  • 对于cpu来说,只需要关注内核空间的线程就可以了。

当然如果只是这样把用户空间的协程和内核空间的线程一一绑定还是没有解决问题的,如果开启的比较多,那么对应的线程也会跟着一起增加,cpu频繁切换的问题还是没有解决,于是就引入了「调度器」的概念。

引入调度器来在各个协程之间切换,cpu只需要关注内核空间的线程即可,这样「解决了cpu在各个协程之间不断切换的问题」。

存在的问题

这样设计虽然解决了cpu频繁切换的问题,但是如果协程A发生了阻塞,肯定会导致协程B无法被执行。而且如果计算机是多核,那么是无法利用到多核的优势的。显然是不合理的。

对于多核的计算机,在内核空间可以开启多个线程(具体开启几个由计算内核决定,人为无法控制),所以问题的核心点就转移到了协程调度器上面,不管是什么语言,「协程调度器」做的越好,相对的「cpu利用率」也就越高。

Golang 的 GMP:并发编程的艺术,golang,网络,服务器

go对协程的处理

内存控制和灵活调度
  • 首先golang对协程改名为gorountine,并且把多余的空间都去掉,控制每个协程的内存在几KB大小,所以golang可以开启大量协程。
  • golang对协程的调度非常灵活,可以经常在各个协程之间切换。

Golang 的 GMP:并发编程的艺术,golang,网络,服务器

go对早期调度器的处理(GM模型)

golang在早起调度器处理是比较简单的,具体流程如下:

Golang 的 GMP:并发编程的艺术,golang,网络,服务器

  • 首先会有一个全局的go协程队列,并且加锁,防止资源竞争。
  • M获取锁之后会去尝试执行gorountine,执行完毕再把gorountine重新放回队列中。
GM模型存在以下问题
  • 创建、销毁、调度G都需要每个M获取锁,这就形成了激烈的锁竞争。
  • M转移G会造成延迟和额外的系统负载。
  • 系统调用(cpu在M之间切换)导致频繁的线程阻塞和取消阻塞操作增加了系统开销。
  • 比如我再一个G中又开辟了一个G1,那么G1和G当然在一个M上执行是比较合适的,因为存在一些共享内存,但是显然这种调度模式是无法做到的 基于以上问题,golang针对这块做了一些改进,也就是我们今天的主角,GMP模型。

GMP模型

GMP模型简介

GMP模型主要指的是G(gorountine协程),M(thread线程),P(processor处理器)之间的关系。

全局队列

存放等待运行的G。

P的本地队列
  • 存放等待运行的G。
  • P的本地队列存放的G是有数量限制的,一般是不超过256G。
  • 如果创建一个G,是会优先放在p的本地队列中,如果满了则会放到全局队列中去。
P列表
  • 在程序启动的过程时创建。
  • 最多有GOMAXPROCS个(可配置)。
  • 可以通过环境变量$GOMAXPROCS来设置P的个数,也可以在程序中通过runtime.GOMAXPROCS()来设置。
M列表
  • 当前操作系统分配到当前go程序的内核线程数。
  • go语言本身,限制M的最大数量是10000。
  • 可以通过runtime/debug包中的setMaxThreads来设置。
  • 如果有一个M阻塞,则会创建一个新的M。
  • 如果有M空闲,那么会回收或者睡眠。

调度器的设计策略

复用线程
work stealing机制

Golang 的 GMP:并发编程的艺术,golang,网络,服务器

  • M1对应的P上面G1正在执行,G2和G3处于等待中的状态。
  • M2对应的P处于空闲状态。

这种情况下M2对应的P会从M1对应的P的本地队列中把G3偷取过来执行,提高CPU的利用率,这种机制叫做「work stealing机制」。

hand off机制

Golang 的 GMP:并发编程的艺术,golang,网络,服务器

如果M1和M2都在正常执行,但是M1对应的G1发生了阻塞,那么势必会影响到G2的执行,那么GMP是如何解决的呢?

Golang 的 GMP:并发编程的艺术,golang,网络,服务器

  • golang会新创建一个M3,用来接管之前的P1剩下的G(G2)。
  • M1和G1进行绑定再继续执行,执行完毕之后把M1设置为睡眠状态等待下一次被利用,或者直接销毁。
并行利用

并行利用其实比较好理解,其实也就是开启了多少个P,P的个数是有GOMAXPROCS来决定的,一般都会设置为 「CPU核数/2」。

抢占策略

对于传统的co-routine来说,如果一个C和cpu进行了绑定,那么只有他主动释放,另外一个C才能和cpu进行绑定。但是在golang中,如果一个G和cpu进行了绑定,那么时间限制最多为10ms,另外一个G就可以直接和cpu绑定。

抢占策略。

全局队列

Golang 的 GMP:并发编程的艺术,golang,网络,服务器

  • 全局队列的本质是对work stealing的一种补充。
  • 如上图,M2对应的本地队列没有G,会优先从M1的本地队列中偷取。
  • 如果M1的本地队列中也没有G,那么就会从全局队列中去偷取G3。
  • 因为全局队列涉及到加锁和解锁,所以效率相对要低一些。

go的启动周期(M0和G0)

要想了解go的启动周期,首先得了解M0和G0的概念。

M0
  • 在一个进程中是唯一的。
  • 启动程序后编号为0的主线程。
  • 在全局变量runtime.m0中,不需要在heap上分配。
  • 负责初始化操作和启动第一个G。
  • 启动第一个G之后,M0就和其他的M一样了。
G0
  • 在一个线程中是唯一的。
  • 每次启动一个M,都会第一个创建的gorountine,就是G0。
  • G0仅仅用于负责调度其他G,G0不指向任何可执行的函数。
  • 每个M都会有一个自己的G0。
  • 在调度或者系统调用的时候,会使用M切换到G0来调度。
  • M0的G0会放在全局空间。

执行流程

package main
import "fmt"

func main() {
 fmt.Println("Hello World")
}

比如我们看上断代码的执行流程。

初始化操作

在执行到main函数之前,会有一些初始化的操作,比如创建M0,创建G0等等。

Golang 的 GMP:并发编程的艺术,golang,网络,服务器

执行具体函数

当执行main函数的时候,M0已经和其他的M是一样的了,main函数会进入M0对应的p的本地队列中,然后和M0绑定执行,如果执行超时(10ms),则会重新放到M0对应的本地队列中。一直到执行到exit或者panic为止。

Golang 的 GMP:并发编程的艺术,golang,网络,服务器文章来源地址https://www.toymoban.com/news/detail-730566.html

到了这里,关于Golang 的 GMP:并发编程的艺术的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Golang的协程调度器原理及GMP设计思想

    (1) 单进程时代不需要调度器 我们知道,一切的软件都是跑在操作系统上,真正用来干活(计算)的是CPU。早期的操作系统每个程序就是一个进程,知道一个程序运行完,才能进行下一个进程,就是“单进程时代” 一切的程序只能串行发生。 早期的单进程操作系统,面临2个问题

    2024年02月16日
    浏览(44)
  • linux并发服务器 —— linux网络编程(七)

    C/S结构 - 客户机/服务器;采用两层结构,服务器负责数据的管理,客户机负责完成与用户的交互;C/S结构中,服务器 - 后台服务,客户机 - 前台功能; 优点 1. 充分发挥客户端PC处理能力,先在客户端处理再提交服务器,响应速度快; 2. 操作界面好看,满足个性化需求; 3.

    2024年02月09日
    浏览(73)
  • 【网络编程】高性能并发服务器源码剖析

      hello !大家好呀! 欢迎大家来到我的网络编程系列之洪水网络攻击,在这篇文章中, 你将会学习到在网络编程中如何搭建一个高性能的并发服务器,并且我会给出源码进行剖析,以及手绘UML图来帮助大家来理解,希望能让大家更能了解网络编程技术!!! 希望这篇文章能

    2024年04月15日
    浏览(51)
  • Linux学习之网络编程3(高并发服务器)

    Linux网络编程我是看视频学的,Linux网络编程,看完这个视频大概网络编程的基础差不多就掌握了。这个系列是我看这个Linux网络编程视频写的笔记总结。 问题: 根据上一个笔记,我们可以写出一个简单的服务端和客户端通信,但是我们发现一个问题——服务器只能连接一个

    2024年02月01日
    浏览(47)
  • 网络编程(8.14)TCP并发服务器模型

    作业: 1. 多线程中的newfd,能否修改成全局,不行,为什么? 2. 多线程中分支线程的newfd能否不另存,直接用指针间接访问主线程中的newfd,不行,为什么? 多线程并发服务器模型原代码: 1.将newfd改成全局变量效果:  答:不行,因为newfd是全局变量的话,客户端连接后生成

    2024年02月13日
    浏览(45)
  • Linux网络编程:多进程 多线程_并发服务器

    文章目录: 一:wrap常用函数封装 wrap.h  wrap.c server.c封装实现 client.c封装实现 二:多进程process并发服务器 server.c服务器 实现思路 代码逻辑  client.c客户端 三:多线程thread并发服务器 server.c服务器 实现思路 代码逻辑  client.c客户端 ​​​​   read 函数的返回值 wrap.h  wrap

    2024年02月12日
    浏览(54)
  • 计算机网络编程 | 并发服务器代码实现(多进程/多线程)

    欢迎关注博主 Mindtechnist 或加入【Linux C/C++/Python社区】一起学习和分享Linux、C、C++、Python、Matlab,机器人运动控制、多机器人协作,智能优化算法,滤波估计、多传感器信息融合,机器学习,人工智能等相关领域的知识和技术。 专栏:《网络编程》 当涉及到构建高性能的服务

    2024年02月08日
    浏览(71)
  • 【Linux网络编程】高并发服务器框架 线程池介绍+线程池封装

    前言 一、线程池介绍 💻线程池基本概念 💻线程池组成部分 💻线程池工作原理  二、线程池代码封装 🌈main.cpp 🌈ThreadPool.h 🌈ThreadPool.cpp 🌈ChildTask.h  🌈ChildTask.cpp 🌈BaseTask.h 🌈BaseTask.cpp 三、测试效果 四、总结 📌创建线程池的好处 本文主要学习 Linux内核编程 ,结合

    2024年01月16日
    浏览(92)
  • libevent高并发网络编程 - 04_libevent实现http服务器

    链接: C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂 在libevent中,HTTP的实现主要是通过 evhttp 模块来完成的。 evhttp 提供了一个高层次的HTTP服务器接口,可以处理HTTP请求并发送HTTP响应。 在源码中,libevent的HTTP协议处理主要是通过 evhttp 模块来完成的。

    2024年02月15日
    浏览(35)
  • Linux网络编程:线程池并发服务器 _UDP客户端和服务器_本地和网络套接字

    文章目录: 一:线程池模块分析 threadpool.c 二:UDP通信 1.TCP通信和UDP通信各自的优缺点 2.UDP实现的C/S模型 server.c client.c 三:套接字  1.本地套接字 2.本地套 和 网络套对比 server.c client.c threadpool.c   server.c client.c server.c client.c

    2024年02月11日
    浏览(62)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包