Golang面试题四(GMP)

2024-04-17 13:28
文章标签 golang 面试题 gmp

本文主要是介绍Golang面试题四(GMP),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

1.Goroutine 定义

2.GMP 指的是什么

3.GMP模型的简介

全局队列(Global Queue)

P的本地队列

P列表

M列表

4.有关P和M的个数问题

P的数量问题

M的数量问题

P和M何时会被创建

5.调度器P的设计策略

复⽤线程

work stealing机制

hand off机制

利⽤并⾏

抢占

全局G队列

6.“go func()” 经历了什么过程

7.调度器的生命周期

M0

G0

8.Sysmon有什么作用

9.可视化GMP编程


1.Goroutine 定义

定义: Goroutine 是 Go 语言运行时系统(Runtime)管理的用户级线程(User-Level Thread),是一种可以在单个进程中并发执行的执行单元。每个 Goroutine 代表了一个独立的函数调用,可以在程序中并行地执行任务,而无需操作系统层面的线程(OS Thread)支持。

关键特性

  1. 轻量级:相较于操作系统原生线程,Goroutine 的创建、销毁和上下文切换(Context Switching)成本极低,使得开发者可以轻松地在程序中创建大量并发任务,实现高并发处理。

  2. 协程:Goroutines 之间通过协作而非抢占式调度进行切换,这意味着一个 Goroutine 只有在主动放弃 CPU 时间片(如调用系统调用、阻塞在 I/O 操作或显式调用 sync 包中的同步原语)时,运行时才会调度其他等待的 Goroutine 执行。这种非抢占式的调度有助于减少线程上下文切换的开销。

  3. Go 语言原生支持:在 Go 语言中,启动一个新的 Goroutine 非常简单,只需在函数调用前加上关键字 go,如 go someFunction()。编译器会负责将此函数封装为一个可以并发执行的 Goroutine。

  4. 调度器:Go 语言的运行时系统内置了一个高效的调度器(Scheduler),负责管理和调度所有 Goroutines 的执行。调度器可以根据系统资源(如 CPU 核心数)和 Goroutines 的运行状态动态调整其调度策略,确保并发任务高效、公平地执行。

  5. 通信代替共享内存:Go 语言鼓励通过 channels(通道)进行 Goroutines 之间的通信和同步,遵循“不要通过共享内存来通信,而应通过通信来共享内存”的原则,避免了传统并发编程中常见的数据竞争和竞态条件问题。

  6. 栈动态增长:每个 Goroutine 初始时分配一个小栈(通常为几 KB),随着函数调用深度的增加,栈空间不足时会自动增长(可达几 MB),避免了预估栈大小的难题,同时也减少了内存浪费。

综上所述,Goroutine 是 Go 语言中实现并发编程的关键特性,它是一种轻量级、协程化的执行单元,由 Go 语言运行时系统原生支持并高效调度。通过 Goroutines,开发者可以方便、高效地编写并发程序,利用多核处理器能力,同时借助 channels 和其他同步原语确保数据安全和正确性。

2.GMP 指的是什么

  • G( Goroutine): 我们所说的协程,为用户级的轻量级线程,每个Goroutine对象中的sched保存着其上下文信息。
  • M( Machine): 对内核级线程的封装,数量对应真实的 CPU 数(真正干活的对象)。
  • P( Processor): 即为 G 和 M 的调度对象,用来调度 G 和 M 之间的关联关系,其数量可通过 GOMAXPROCS()来设置,默认为核心数。

3.GMP模型的简介

在Go中,线程是运行goroutine的实体,调度器的功能是把可运行的goroutine分配到工作线程上

全局队列(Global Queue)

存放等待运行的G。

P的本地队列

  • 同全局队列类似,存放的也是等待运行的G
  • 存的数量有限,不超过256个。
  • 新建G'时,G'优先加入到P的本地队列,如果队列满了,则会把本地队列中一半的G移动到全局队列。

P列表

所有的P都在程序启动时创建,并保存在数组中,最多有GOMAXPROCS(可配置)个。

M列表

  • 当前操作系统分配到当前Go程序的内核线程数
  • 线程想运行任务就得获取P,从P的本地队列获取G,P队列为空时,M也会尝试从全局队列拿一批G放到P的本地队列,或从其他P的本地队列偷一半放到自己P的本地队列。
  • M运行G,G执行之后,M会从P获取下一个G,不断重复下去。

Goroutine调度器和OS调度器是通过M结合起来的,每个M都代表了1个内核线程,OS调度器负责把内核线程分配到CPU的核上执行。

4.有关P和M的个数问题

P的数量问题

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

M的数量问题

  • go语言本身的限制:go程序启动时,会设置M的最大数量,默认10000.但是内核很难支持这么多的线程数,所以这个限制可以忽略。
  • runtime/debug中的SetMaxThreads函数,设置M的最大数量
  • 一个M阻塞了,会创建新的M。

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

P和M何时会被创建

  • P何时创建:在确定了P的最大数量n后,运行时系统会根据这个数量创建n个P。
  • M何时创建:没有足够的M来关联P并运行其中的可运行的G。比如所有的M此时都阻塞住了,而P中还有很多就绪任务,就会去寻找空闲的M,而没有空闲的,就会去创建新的M。

5.调度器P的设计策略

复⽤线程

避免频繁的创建、销毁线程,⽽是对线程的复⽤。

work stealing机制

当本线程⽆可运⾏的G时,尝试从其他线程绑定的P偷取G,⽽不是销毁线程。

hand off机制

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

利⽤并⾏

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

抢占

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

全局G队列

在新的调度器中依然有全局G队列,当P的本地队列为空时,优先从全局队列获取,如果全局队列为空时则通过work stealing机制从其他P的本地队列偷取G。

6.“go func()” 经历了什么过程

1、我们通过 go func()来创建一个goroutine;

2、有两个存储G的队列,一个是局部调度器P的本地队列、一个是全局G队列。新创建的G会先保存在P的本地队列中,如果P的本地队列已经满了就会保存在全局的队列中;

3、G只能运行在M中,一个M必须持有一个P,M与P是1:1的关系。M会从P的本地队列弹出一个可执行状态的G来执行,如果P的本地队列为空,就会想其他的MP组合偷取一个可执行的G来执行;

4、一个M调度G执行的过程是一个循环机制;

5、当M执行某一个G时候如果发生了syscall或则其余阻塞操作,M会阻塞,如果当前有一些G在执行,runtime会把这个线程M从P中摘除(detach),然后再创建一个新的操作系统的线程(如果有空闲的线程可用就复用空闲线程)来服务于这个P;

6、当M系统调用结束时候,这个G会尝试获取一个空闲的P执行,并放入到这个P的本地队列。如果获取不到P,那么这个线程M变成休眠状态, 加入到空闲线程中,然后这个G会被放入全局队列中。

7.调度器的生命周期

M0

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

G0

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

我们来跟踪一段代码

package mainimport "fmt"func main() {fmt.Println("Hello world")
}
  1.  runtime创建最初的线程m0和goroutine g0,并把2者关联。
  2. 调度器初始化:初始化m0、栈、垃圾回收,以及创建和初始化由GOMAXPROCS个P构成的P列表。
  3. 示例代码中的main函数是main.main,runtime中也有1个main函数——runtime.main,代码经过编译后,runtime.main会调用main.main,程序启动时会为runtime.main创建goroutine,称它为main goroutine吧,然后把main goroutine加入到P的本地队列。
  4. 启动m0,m0已经绑定了P,会从P的本地队列获取G,获取到main goroutine。
  5. G拥有栈,M根据G中的栈信息和调度信息设置运行环境
  6. M运行G
  7. G退出,再次回到M获取可运行的G,这样重复下去,直到main.main退出,runtime.main执行Defer和Panic处理,或调用runtime.exit退出程序。

调度器的生命周期几乎占满了一个Go程序的一生,runtime.main的goroutine执行之前都是为调度器做准备工作,runtime.main的goroutine运行,才是调度器的真正开始,直到runtime.main结束而结束。

8.Sysmon有什么作用

sysmon是一个管理线程或者说守护线程,其是对GMP调度架构的补充和兜底。

GMP的调度完全是主动协作式的调度。主动协作式的调度性能很高,但是在某些情况下会出现单个goroutine长期占据时间片甚至一直占据时间片的情况。
比如:

  1. 某个goroutine不执行主动调度、不调用系统调用、不做函数调用,就会一直运行直到goroutine退出;
  2. 某个goroutine处于syscall状态时也无法触发主动调度,可能会造成该goroutine长时间占据时间片;

sysmon的作用就是处理类似上面情况,其主要的工作内容有:

  1. 定期查看netpoll有无就绪的任务,防止netpoll阻塞队列中的goroutine饥饿;
  2. 定期查看是否有p长时间(10ms)处于syscall状态,如有则将p的持有权释放以执行其他g;
  3. 定期查看是否有p长时间(10ms)没有调度,如有则对当前m发送信号,触发基于信号的异步抢占调度;

9.可视化GMP编程

方式1:go tool trace

方式2:Debug trace

参考:2、Golang的协程调度器原理及GMP设计思想 · 语雀

这篇关于Golang面试题四(GMP)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/911919

相关文章

荣耀嵌入式面试题及参考答案

在项目中是否有使用过实时操作系统? 在我参与的项目中,有使用过实时操作系统。实时操作系统(RTOS)在对时间要求严格的应用场景中具有重要作用。我曾参与的一个工业自动化控制项目就采用了实时操作系统。在这个项目中,需要对多个传感器的数据进行实时采集和处理,并根据采集到的数据及时控制执行机构的动作。实时操作系统能够提供确定性的响应时间,确保关键任务在规定的时间内完成。 使用实时操作系统的

一些其他面试题

阿里二面:那你来说说定时任务?单机、分布式、调度框架下的定时任务实现是怎么完成的?懵了。。_哔哩哔哩_bilibili 1.定时算法 累加,第二层每一个格子是第一层的总时间400 ms= 20 * 20ms 2.MQ消息丢失 阿里二面:高并发场景下引进消息队列有什么问题?如何保证消息只被消费一次?真是捏了一把汗。。_哔哩哔哩_bilibili 发送消息失败

zookeeper相关面试题

zk的数据同步原理?zk的集群会出现脑裂的问题吗?zk的watch机制实现原理?zk是如何保证一致性的?zk的快速选举leader原理?zk的典型应用场景zk中一个客户端修改了数据之后,其他客户端能够马上获取到最新的数据吗?zk对事物的支持? 1. zk的数据同步原理? zk的数据同步过程中,通过以下三个参数来选择对应的数据同步方式 peerLastZxid:Learner服务器(Follo

java常用面试题-基础知识分享

什么是Java? Java是一种高级编程语言,旨在提供跨平台的解决方案。它是一种面向对象的语言,具有简单、结构化、可移植、可靠、安全等特点。 Java的主要特点是什么? Java的主要特点包括: 简单性:Java的语法相对简单,易于学习和使用。面向对象:Java是一种完全面向对象的语言,支持封装、继承和多态。跨平台性:Java的程序可以在不同的操作系统上运行,称为"Write once,

【Kubernetes】常见面试题汇总(三)

目录 9.简述 Kubernetes 的缺点或当前的不足之处? 10.简述 Kubernetes 相关基础概念? 9.简述 Kubernetes 的缺点或当前的不足之处? Kubernetes 当前存在的缺点(不足)如下: ① 安装过程和配置相对困难复杂; ② 管理服务相对繁琐; ③ 运行和编译需要很多时间; ④ 它比其他替代品更昂贵; ⑤ 对于简单的应用程序来说,可能不

【附答案】C/C++ 最常见50道面试题

文章目录 面试题 1:深入探讨变量的声明与定义的区别面试题 2:编写比较“零值”的`if`语句面试题 3:深入理解`sizeof`与`strlen`的差异面试题 4:解析C与C++中`static`关键字的不同用途面试题 5:比较C语言的`malloc`与C++的`new`面试题 6:实现一个“标准”的`MIN`宏面试题 7:指针是否可以是`volatile`面试题 8:探讨`a`和`&a`

Laravel 面试题

PHP模块 PHP7 和 PHP5 的区别,具体多了哪些新特性? 性能提升了两倍 结合比较运算符 (<=>) 标量类型声明 返回类型声明 try…catch 增加多条件判断,更多 Error 错误可以进行异常处理 匿名类,现在支持通过new class 来实例化一个匿名类,这可以用来替代一些“用后即焚”的完整类定义 …… 了解更多查看文章底部链接 PHP7 新特性 为什么 PHP

Golang进程权限调度包runtime

关于 runtime 包几个方法: Gosched:让当前线程让出 cpu 以让其它线程运行,它不会挂起当前线程,因此当前线程未来会继续执行GOMAXPROCS:设置最大的可同时使用的 CPU 核数Goexit:退出当前 goroutine(但是defer语句会照常执行)NumGoroutine:返回正在执行和排队的任务总数GOOS:目标操作系统NumCPU:返回当前系统的 CPU 核数量 p

Golang 网络爬虫框架gocolly/colly(五)

gcocolly+goquery可以非常好地抓取HTML页面中的数据,但碰到页面是由Javascript动态生成时,用goquery就显得捉襟见肘了。解决方法有很多种: 一,最笨拙但有效的方法是字符串处理,go语言string底层对应字节数组,复制任何长度的字符串的开销都很低廉,搜索性能比较高; 二,利用正则表达式,要提取的数据往往有明显的特征,所以正则表达式写起来比较简单,不必非常严谨; 三,使

Golang网络爬虫框架gocolly/colly(四)

爬虫靠演技,表演得越像浏览器,抓取数据越容易,这是我多年爬虫经验的感悟。回顾下个人的爬虫经历,共分三个阶段:第一阶段,09年左右开始接触爬虫,那时由于项目需要,要访问各大国际社交网站,Facebook,myspace,filcker,youtube等等,国际上叫得上名字的社交网站都爬过,大部分网站提供restful api,有些功能没有api,就只能用http抓包工具分析协议,自己爬;国内的优酷、