首页 > 其他分享 >go语言调度gmp数据结构

go语言调度gmp数据结构

时间:2023-05-13 17:48:00浏览次数:48  
标签:数据结构 goroutine 调度 线程 处理器 go gmp stack

go语言调度gmp数据结构

img

  • g 表示goroutine,它是待执行的任务
  • m 表示操作系统的线程,它由操作系统的调度器调度和管理
  • p 表示处理器,可以把它看作在线程上运行的本地调度器

G

goroutine是go语言调度器中待执行的任务,它在运行时调度器中的地位和线程在操作系统中的地位差不多,但是它占用的内存空间更小,也降低了上下文切换的开销

与栈相关的字段

type g struct {
	// Stack parameters.
	// stack describes the actual stack memory: [stack.lo, stack.hi).
	// stackguard0 is the stack pointer compared in the Go stack growth prologue.
	// It is stack.lo+StackGuard normally, but can be StackPreempt to trigger a preemption.
	// stackguard1 is the stack pointer compared in the C stack growth prologue.
	// It is stack.lo+StackGuard on g0 and gsignal stacks.
	// It is ~0 on other goroutine stacks, to trigger a call to morestackc (and crash).
	stack       stack   // offset known to runtime/cgo
	stackguard0 uintptr // offset known to liblink
	stackguard1 uintptr // offset known to liblink
}

stack字段描述了当前goroutine的栈内存范围[stack.lo,stack.hi],stack
guard0可以用于调度器抢占式调度,除stackguard0外,goroutine中还包含另外3个与抢占密切相关的字段

type g struct {
	preempt       bool // preemption signal, duplicates stackguard0 = stackpreempt
	preemptStop   bool // transition to _Gpreempted on preemption; otherwise, just deschedule
	preemptShrink bool // shrink stack at synchronous safe point
}

每个goroutine上都持有两个分别存储defer和panic对应结构体的链表

type g struct {
	_panic    *_panic // innermost panic - offset known to liblink
	_defer    *_defer // innermost defer
}

还有几个比较重要的字段

type g struct {
	m         *m      // current m; offset known to arm liblink
	sched     gobuf
    atomicstatus atomic.Uint32
}
  • m——当前goroutine占用的线程,可能为空
  • sched——存储goroutine的调度相关数据
  • atomicstatus——goroutine的状态
type gobuf struct {
	// The offsets of sp, pc, and g are known to (hard-coded in) libmach.
	//
	// ctxt is unusual with respect to GC: it may be a
	// heap-allocated funcval, so GC needs to track it, but it
	// needs to be set and cleared from assembly, where it's
	// difficult to have write barriers. However, ctxt is really a
	// saved, live register, and we only ever exchange it between
	// the real register and the gobuf. Hence, we treat it as a
	// root during stack scanning, which means assembly that saves
	// and restores it doesn't need write barriers. It's still
	// typed as a pointer so that any other writes from Go get
	// write barriers.
	sp   uintptr
	pc   uintptr
	g    guintptr
	ctxt unsafe.Pointer
	ret  uintptr
	lr   uintptr
	bp   uintptr // for framepointer-enabled architectures
}
  • sp——栈指针
  • pc——程序计数器
  • g——持有runtime.gobuf的goroutine
  • ret——系统调用的返回值

这些内容会在调度器保存或者恢复上下文时用到,其中的栈指针和程序计数器用来存储或者恢复寄存器中的值,改变程序即将执行的代码

M

go语言并发模型中的M是操作系统线程。调度器最多可以创建10000个线程,但是其中大多数线程不会执行用户代码(可能陷入系统调用),最多只会有GOMAXPROCS个活跃线程能够正常运行。

默认情况下,运行时会将GOMAXPROCS设置成当前机器的核数,我们也可以在程序中使用runtime.GOMAXPROCS来改变最大的活跃线程数。

默认情况下,一个四核机器会创建四个活跃的操作系统线程,每一个线程都对应一个运行时中的runtime.m结构体

img

在大多数情况下,我们会使用go语言的默认设置,也就是线程数等于cpu数,默认设置不会频繁触发操作系统的线程调度和上下文切换,所有调度都发生在用户态,由go语言调度器触发,能够减少很多额外开销。

go语言会使用私有结构体runtime.m表示操作系统线程

与goroutine相关的字段

type m struct {
	g0      *g     // goroutine with scheduling stack
    curg    *g     // current running goroutine
}

其中g0是持有调度栈的goroutine,curg是在当前线程上运行的用户goroutine,他们也是操作系统线程仅仅关心的两个goroutine

g0是运行时中比较特殊的一个goroutine,它会深度参与运行时的调度过程,包括goroutine的创建、大内存分配和cgo函数的执行

img

runtime.m结构体中还存在3个与处理器相关的字段,它们分别表示正在运行代码的处理器p、暂存的处理器nextp和执行系统调用之前使用线程的处理器oldp

type m struct {
	p             puintptr // attached p for executing go code (nil if not executing go code)
	nextp         puintptr
	oldp          puintptr // the p that was attached before executing a syscall
}

除了上面介绍的字段外,runtime.m还包含大量与线程状态、锁、调度、系统调用有关的字段

P

调度器中的处理器p是线程和goroutine的中间层,它能提供线程需要的上下文,也会负责调度线程上的等待队列。通过处理器p的调度,每一个内核线程都能够执行多个goroutine,它能在goroutine进行一些I/O操作时及时让出计算资源,提高线程的利用率。

因为调度器在启动时就会创建GOMAXPROCS个处理器,所以go语言程序的处理器数量一定会等于GOMAXPROCS,这些处理器会绑定到不同的内核线程上。

runtime.p是处理器的运行时表示,作为调度器的内部实现,它包含的字段非常多,其中包括与性能追踪、垃圾收集和计时器相关的字段。这些字段非常重要,但是这里就不展示了,我们主要关注处理器中的线程和运行队列

type p struct {
    m           muintptr   // back-link to associated m (nil if idle)
    // Queue of runnable goroutines. Accessed without lock.
	runqhead uint32
	runqtail uint32
	runq     [256]guintptr
	// runnext, if non-nil, is a runnable G that was ready'd by
	// the current G and should be run next instead of what's in
	// runq if there's time remaining in the running G's time
	// slice. It will inherit the time left in the current time
	// slice. If a set of goroutines is locked in a
	// communicate-and-wait pattern, this schedules that set as a
	// unit and eliminates the (potentially large) scheduling
	// latency that otherwise arises from adding the ready'd
	// goroutines to the end of the run queue.
	//
	// Note that while other P's may atomically CAS this to zero,
	// only the owner P can CAS it to a valid G.
	runnext guintptr
}

反向存储的线程维护着线程与处理器之间的关系,而runhead、runqtail和runq三个字段表示处理器持有的运行队列,其中存储着待执行的goroutine列表,runnext中是线程下一个需要执行的goroutine

处理器的状态:

  • _Pidle 处理器没有运行用户代码或者调度器,被空闲队列或者改变其状态的结构持有,运行队列为空
  • _Prunning 被线程m持有,并且正在执行用户代码或者调度器
  • _Psyscall 没有执行用户代码,当前线程陷入系统调用
  • _Pgcstop 被线程m持有,当前处理器由于垃圾收集被停止
  • _Pdead 当前处理器已经停用

标签:数据结构,goroutine,调度,线程,处理器,go,gmp,stack
From: https://www.cnblogs.com/zpf253/p/17397789.html

相关文章

  • 浅谈如何使用 github.com/yuin/gopher-lua
    最近熟悉go项目时,发现项目中有用到github.com/yuin/gopher-lua这个包,之前并没有接触过,特意去看了官方文档和找了些网上的资料,特此记录下。本次介绍计划分为两篇文章,这一次主要介绍github.com/yuin/gopher-lua这个包的介绍以及基础使用,下一边将介绍github.com/yuin/goph......
  • CS61B_project_gold
    题目描述 1importstaticorg.junit.Assert.*;2importorg.junit.Test;34publicclassTestArrayDequeGold{5@Test6publicvoidtestStudentArrayDeque(){7StudentArrayDeque<Integer>testArray=newStudentArrayDeque<>......
  • go相当详细的基础语法
    运行命令gorunxx.go或者gobuildxx.go+./xxpackagemainimport( "fmt")funcmain(){ fmt.Println("helloworld")}​基础语法packagemainimport( "fmt" "math")funcmain(){ vara="ivanlee&......
  • MongoDB 功能详解之时间序列集合(Time Series Collections)
    MongoDB功能详解之时间序列集合(TimeSeriesCollections)      时间序列集合(TimeSeriesCollections):MongoDB5.0版本中的新功能。时间序列数据是一系列数据点,通过分析这些随时间变化的数据点而获得对数据的深刻理解。时间序列数据通常由以下组成部分组成:时间:数......
  • [golang] logrus日志包如何自定义级别
    在Logrus中,可以使用logrus.Level类型定义自定义的日志级别,以便更精细地控制日志输出。以下是使用Logrus自定义级别的基本步骤:首先,在代码中使用 logrus.New() 方法创建一个新的logrus.Logger对象,并使用 AddHook() 方法注册所需的Hook对象(可选)。例如,以下代码创建了一个......
  • 数据结构--树和森林
    数据结构--树和森林树和森林森林是m棵互补相交的树的集合.将树去掉根结点可以变成森林,将森林的每个树全部加上根结点可以变成一颗树.1.双亲表示法数据域:存放数据双亲域:存放双亲结点在数组当中的位置.特点:找双亲容易,找孩子难.双亲表示法使用结构体数组存储首先定......
  • 用go设计开发一个自己的轻量级登录库/框架吧(业务篇)
    用go设计开发一个自己的轻量级登录库/框架吧(业务篇)本篇会讲讲框架的登录业务的实现。实现三种登录模式:同一用户只能登录一次同一用户多次登录多token同一用户多次登录共享一个token源码:weloe/token-go:alightloginlibrary(github.com)存储结构首先从我们要考虑是......
  • 【acwing】Django课程笔记
     工程课Linux-8.0.SSH的简易安全配置-AcWing  (避免服务器被偷家)怎样修改远程登录的端口?_弹性云服务器ECS_常见问题_登录与连接_远程连接类_华为云(huaweicloud.com)vim/etc/ssh/sshd_configservicesshdrestart ......
  • Django中使用分页器
    场景描述有一个get请求,如果传递的有id,则返回具体某一个菜品的信息,如果没有,则返回当前默认分页的内容,获取用户传递的分页参数进行查询。分页说明如果数据量很大,几百上千条,一般是需要使用分页器的,这样可以对用户,对服务器,对网络都比较友好。如果当前框架不提供分页查询功能,也就是......
  • Django如何把SQLite数据库转换为Mysql数据库
    大部分新手刚学Django开发的时候默认用的都是SQLite数据库,上线部署的时候,大多用的却是Mysql。那么我们应该如何把数据库从SQLite迁移转换成Mysql呢?之前我们默认使用的是SQLite数据库,我们开发完成之后,里面有许多数据。如果我们想转换成Mysql数据库,那我们先得把旧数据从SQLite导出......