当前位置: 首页 > news >正文

【golang】调度系列之P

调度系列
调度系列之goroutine
调度系列之m

在前面两篇中,分别介绍了G和M,当然介绍的不够全面(在写后面的文章时我也在不断地完善前面的文章,后面可能也会有更加汇总的文章来统筹介绍GMP)。但是,抛开技术细节,我想经过前面两篇,应该已经对GMP建立了基本的认知。GMP本质就是个任务处理系统,G是任务,M是worker,runtime创建一定数量的worker来运行任务。

一个简单的任务处理系统,只任务和worker两个对象足够胜任。从这个角度来说,似乎GM就足够了,并不需要P。实际上,golang的调度系统最开始就是采用了GM模型。然而golang的调度系统显然不是简单的任务处理系统,而是一个复杂度非常高的任务系统,在迭代过程中遇到了很多的问题。为了解决这些问题,runtime在G和M之间引入了一个中间层,就是P。任何计算机科学领域的问题都可以通过引入一个中间层来解决,越来越觉得这句话是大道至简、大音希声。

关于runtime调度系统演进过程的细节可以参见google的文档google doc,这里就不过多介绍。

文章目录

  • 状态图
  • P的结构

状态图

在介绍具体的细节前,先介绍下P整体的状态的流转。P的状态相对G的状态要简单一些,相对M的状态则要复杂得多。P的状态和G一样,具有明确定义的可枚举的状态值。

  • idle。P处于空闲状态。此时的P没有被用来运行用户的goroutine或者时runtime的代码,也可以说P并没有被具体的m持有。大多数情况下,处于idle状态的P应该位于全局的idle list中。
    为什么这里说大多数呢?这和runtime的实现相关。在runtime的实现中,由其他状态(syscall、gcstop)转变为running时,会先转变为idle,再由idle转变为running。
  • running。P处于运行状态。此时P被m持有,被用来运行用户的goroutine或者runtime的代码。对应的m可能处于running、spinning状态。running状态应该是最好理解的状态,P大部分时间也应该处于running状态。
  • syscall。P处于系统调用状态。当P上运行的g进入系统调用时m会与P解绑,将P置为syscall状态。当系统调用结束时,m会首先尝试获取原来的P,再尝试获取idle list中的P,如果拿不到则挂起。
    P进入syscall时根据系统调用时间长短有两种选择,当系统调用时间较短时P处于syscall等待m;当系统调用时间比较长时,会调用handoffp将p转移给其他的m继续执行g。
    以上的操作都是在runtime中通过pre hook和post hook的方式实现的。
  • gcstop。P处于STW状态。P由running转变为gcstop时会和当前的m解绑,当strat the word时,会重新驱动m来获取P。
  • dead。P处于死亡状态。只有调用procresize方法缩减P的数量时才会触发,调用p.destroy方法,释放所有的资源。当procresize只有在stop the world时才会调用,所以dead只能由gcstop转变而来(不是很确定)。

状态流转图如下。

简单介绍P的状态流转。

  • runtime初始化时会调用procresize创建GOMAXPROCS个P,初始化时P的状态会在p.init中被设为gcstop,但最终会被设为idle,并加入全局的idle list中。
  • 当有goroutine创建(newproc)或者goroutine就绪(goready)时,会调用wakep。如果存在idle的p并且没有自旋的m,wakep启动m将idle的P转变为running。
  • running状态的P可以转变为gcstop、idle、syscall。
    • 当m在运行findrunnable时发现runtime需要gc(gcwaiting!=0),会将P转变为gcstop状态并和P进行解绑;
    • 当m在findrunnable中找不到可运行的任务时,会将P转变为idle状态并挂起;
    • 当m(g)调用系统调用时,会将P转变为syscall状态;
    • 另外在一些情况下,m也会调用handoffp主动转移P的持有,比如runtime判断一个系统调用的时间比较长是,会将P转变为syscall,然后主动handoffp转移P的持有;

P的结构

P的字段很多,这里同样就挑几个重要的说明。可见下图。

  • 资源相关。
    引入P的一个最大的好处就是减少了资源的浪费。在GM相关的模型中,运行goroutine相关的资源是分配给每个M的,比如内存。但是在生产系统中,真正运行的m的数量占总数的比例很小,大多数可能在阻塞中。这样就导致了资源的浪费。引入P后,相关的资源都分配给P,m在执行时首先要获取P。这样就大大减少了资源的浪费。所以P中有很多资源相关的字段,比如mcache、pcache、mspan等。
    另外,比如向timer类似的数据结构,也从全局对象变为每个P持有的对象。这样做可以大大减少并发冲突。因为同一个P上同时最多只有一个goroutine执行,并发只存在跨P的操作中。
  • 调度相关。
    之前在goroutine和M的介绍中提到了很多调度相关的内容。但其中的内容都是属于主动调度。runtime中真正的异步抢占或者说异步调度是依靠sysmon来实现的。sysmon是一个独立的m,并且其运行不需要持有P。sysmon会定期的扫描每个P,如果goroutine占据P的时间超过10ms,怎会触发异步抢占。其中schedtick、syscalltick、sysmontick等字段都是和sysmon相关。关于sysmon,我们会在后面再详细介绍。

至此,对G、M、P都有了单独的介绍和认识,对runtime的调度系统,应该有了初步和大概的认识。注意,runtime中还有一个全局的schedt对象,但是该对象仅保存一些全局的数据,而不负责具体的调度,所以不单独介绍。后面我们会结合前面G、M、P的介绍,对调度整体进行介绍,并陆续地针对细节进行补充。

http://www.lryc.cn/news/170493.html

相关文章:

  • Vue3中watch用法
  • 组里来了一个实习生,一行代码引发了一个惨案
  • 随手笔记(四十五)——idea git冲突
  • chacha20 算法流程
  • 准备篇(三)Python 爬虫第三方库
  • 从零开始的PICO开发教程(4)-- VR世界 射线传送、旋转和移动
  • 防止攥改之水印功能组件
  • iOS 17 适配 Xcode 15 问题
  • Element Plus 快速开始
  • 华为云云耀云服务器L实例评测|StackEdit中文版在线Markdown笔记工具
  • MyEclipse报错javax/persistence/EntityManagerFactory
  • 【MySQL进阶】SQL性能分析
  • 在SpringBoot项目中整合SpringSession,基于Redis实现对Session的管理和事件监听
  • 浅析vue中computed,method,watch,watchEffect的区别
  • activiti7的数据表和字段的解释
  • Java手写Trie树和Trie树应用拓展案例
  • alova.js快速入门教程
  • 获取IP地址-根据IP获取位置信息
  • Android13适配-Google官方照片视频选择器
  • 云计算的发展趋势和挑战
  • PyG-GAT-Cora(在Cora数据集上应用GAT做节点分类)
  • java专项练习(验证码)
  • MS1861 视频处理与显示控制器 HDMI转MIPI LVDS转MIPI带旋转功能 图像带缩放,旋转,锐化
  • 广州华锐互动:利用VR复原文化遗址,沉浸式体验历史文物古迹的魅力
  • 微信小程序——事件监听
  • View绘制流程的源码所得
  • 企业级数据仓库-理论知识
  • 解决flutter不识别yaml里面配置的git项目
  • rust结构体
  • Python - 小玩意 - 键盘记录器