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

单页销售网站模板南宁市建设信息网站

单页销售网站模板,南宁市建设信息网站,影响网站排名的因素,加强网站建设任务窃取调度器和抢占式调度器前言GMP的简单介绍什么是GMP?任务窃取调度器抢占式调度器基于协作的抢占式调度器基于信号的抢占式调度前言 每一个OS线程都有一个固定大小的内存块(一般会是2MB)来做栈,这个栈会用来存储当前正在被调…

任务窃取调度器和抢占式调度器

  • 前言
  • GMP的简单介绍
    • 什么是GMP?
  • 任务窃取调度器
  • 抢占式调度器
    • 基于协作的抢占式调度器
    • 基于信号的抢占式调度

前言

每一个OS线程都有一个固定大小的内存块(一般会是2MB)来做栈,这个栈会用来存储当前正在被调用或挂起(指在调用其它函数时)的函数的内部变量。这个固定大小的栈同时很大又很小。因为2MB的栈对于一个小小的goroutine来说是很大的内存浪费,比如对于我们用到的,一个只是用来WaitGroup之后关闭channel的goroutine来说。而对于go程序来说,同时创建成百上千个goroutine是非常普遍的,如果每一个goroutine都需要这么大的栈的话,那这么多的goroutine就不太可能了。除去大小的问题之外,固定大小的栈对于更复杂或者更深层次的递归函数调用来说显然是不够的。修改固定的大小可以提升空间的利用率,允许创建更多的线程,并且可以允许更深的递归调用,不过这两者是没法同时兼备的。
相反,一个goroutine会以一个很小的栈开始其生命周期,一般只需要2KB。一个goroutine的栈,和操作系统线程一样,会保存其活跃或挂起的函数调用的本地变量,但是和OS线程不太一样的是,一个goroutine的栈大小并不是固定的;栈的大小会根据需要动态地伸缩。而goroutine的栈的最大值有1GB,比传统的固定大小的线程栈要大得多,尽管一般情况下,大多goroutine都不需要这么大的栈。

以上内容来自于《Go语言圣经》中对于goroutine的诠释。这篇博客主要是记录goroutine中调度的任务窃取调度器和抢占式调度器

GMP的简单介绍

  GMP 模型是 Go 语言​​并发调度器​​的核心设计,它让 Go 能够高效、合理地组织和管理成千上万个 goroutine

什么是GMP?

  • ​​G​​ - ​​Goroutine​​ (协程)
      这是 Go 并发的基本单位。它是一种用户态的轻量级线程,由 Go 运行时(runtime)管理,而不是操作系统

  • M​​ - ​​Machine​​ (工作线程/内核线程)
    M 是真正在操作系统线程上执行的实体。它与内核线程是 1:1 的关系。M 的主要职责就是执行 G 的代码。一个 M 必须持有一个 P 才能运行 Go 的代码。

  • ​​P​​ - ​​Processor​​ (调度器/上下文)
    P 是 G 和 M 之间的协调者,是 Go 调度器的关键创新。你可以把它看作一个“所需的运行上下文”或“资源包”。P 的数量决定了 Go 程序中真正可同时运行的 G 的最大数量,默认等于当前机器的 ​​CPU 逻辑核心数​​(可通过 GOMAXPROCS环境变量调整)。每个 P 都维护着一个​​本地运行队列(Local Run Queue, LRQ)​​,用于存放本地的 G。这极大地减少了全局锁的竞争。

任务窃取调度器

任务窃取调度器是GMP模型中的一个补充,首先来看一下GMP模型是如何运作的

  1. ​​G 的创建​​:当你执行 go func(…)时,一个新的 G 被创建。优先会加入到​​当前 P 的本地运行队列​​中。如果队列已满,则放入​​全局队列​​。
  2. ​​M 与 P 绑定:一个​​空闲的 M​​ 会尝试获取一个​​空闲的 P​​。如果获取成功,这个 M 就被激活,开始寻找 G 来执行。
  3. ​​M 寻找 G(执行)​​:M 会按照以下顺序寻找 G:从当前 P 的本地队列获取​​:这是最快的方式,几乎无锁。​​从全局队列获取​​:为了防止全局队列中的 G 被“饿死”,调度器会每隔一段时间就去全局队列检查一次。​​任务窃取Work-Stealing)​​:如果其他方式都找不到 G,当前 M 会随机选择另一个 P,并尝试从它的本地队列中“偷”一半的 G 过来执行。

任务窃取调度器的工作流程

  1. 检查本地运行队列(LRQ)​​:这是常规操作,但既然 P 是空闲的,这里肯定为空。
  2. 检查全局运行队列(GRQ)​​:它会检查全局队列中是否有等待的 G。为了防止全局队列中的 G 被“饿死”,调度器规定每调度 61 次(通过一个计数器 schedtick控制),就会优先从全局队列获取 G。
  3. 检查网络轮询器(Netpoller)​​:检查是否有网络 IO 操作已经准备就绪的 G。如果有,就唤醒它来执行。
  4. 任务窃取(Work-Stealing)​​:如果以上地方都找不到工作,P 就会开始“窃取”行为。(窃取者会尝试从其他P中的​​本地运行队列的尾部​​偷走一半的 Goroutine

抢占式调度器

基于协作的抢占式调度器

首先在了解这个调度器的如何工作之前,需要知道几点前置知识

  • 系统监控线程(sysmon)每10ms会扫描所有运行中的G,当发现G运行的时间超过10ms或者GC需要STW的时候,会将stackguard0字段设置StackPreemptStackPreempt=0xfffffade是最大地址值),代码类似于这样:
    atomic.Storeuintptr(&gp.stackguard0, StackPreempt)
    
  • 由于栈是“向下”增长的,使用的时候也是先从高地址的空间开始使用(其实这也就是为什么要将stackguard0设置为StackPreempt,在了解该调度器的时候,心里一定要有这个概念)。

前置知识了解完,现在开始说这个调度器的执行步骤
1、在编译期的时候,会在函数入口处插入指令,用来检查比较记录栈顶位置的寄存器SPstackguard0中值的大小
2、如果SP中的值小于stackguard0的值,说明当前有两种情况。

  • 第一种情况:检查stackguard0的值是否为StackPreempt,如果是的话,说明当前要执行垃圾回收的逻辑,需要暂停当前当前程序
  • 第二种情况,也就是stackguard0的值不是StackPreempt,此时的SP中的值小于stackguard0的值,说明当前栈空间不足需要扩容

为什么说SP中的值小于stackguard0的值(前提是stackguard0的值不为StackPreempt)就需要扩容?
首先来看一张图

高地址
├─────────────┤ 
│  正常栈空间  │ ← SP通常在这里
├─────────────┤ ← stackguard0 (安全线)
│  缓冲区928B  │ ← 当SP接近这里时触发检查
├─────────────┤ ← stack.lo (真正的栈底)
│    危险!    │
└─────────────┘
低地址

SP指向的位置小于stackguard0时,其实就已经超出了限定的“安全范围”,此时SP指向的位置处于缓冲区,还没有超过当前协程栈的边界

基于信号的抢占式调度

  基于协作的抢占式调度有一个问题在于它需要程序中有函数调用才有机会触发,如果没有函数调用,那可能永远没办法执行调度的逻辑。为了解决这个问题,有引入了基于信号的抢占式调度。下面来看它的工作流程

  1. ​​初始化信号处理​​:在Go程序启动时,注册SIGURG信号的处理函数为runtime.doSigPreempt
  2. ​​触发抢占​​:当需要抢占某个Goroutine时(例如GC栈扫描需要挂起Goroutine),运行时会调用runtime.suspendG函数,该函数将Goroutine标记为可抢占(设置preemptStop为true),然后调用runtime.preemptM
  3. 发送信号​​:runtime.preemptM通过runtime.signalM向目标线程发送SIGURG信号。
  4. ​​中断处理​​:目标线程收到SIGURG信号,操作系统中断当前线程的执行,并跳转到预先注册的信号处理函数runtime.doSigPreempt
  5. 修改执行上下文​​:在runtime.doSigPreempt中,通过runtime.sigctxt.pushCall修改被中断线程的上下文。具体来说,它会在栈上模拟一个函数调用,使得信号处理返回后,程序会跳转到runtime.asyncPreempt函数。
  6. ​​执行异步抢占​​:当信号处理返回后,程序执行runtime.asyncPreempt(汇编函数),该函数会保存寄存器状态,然后调用runtime.asyncPreempt2(Go函数)。
  7. 处理抢占​​:runtime.asyncPreempt2调用runtime.preemptPark,该函数将当前Goroutine的状态从_Grunning改为_Gpreempted,然后调用runtime.schedule触发调度,让出当前线程。
  8. ​​重新调度​​:调度器runtime.schedule会选择一个就绪的Goroutine在当前线程上执行。

流程图如下

运行时runtime.sighandler信号处理runtime.suspendG目标线程操作系统runtime.preemptMdoSigPreemptsigctxt.pushCallasyncPreemptasyncPreempt2preemptPark调度器schedule新Goroutine1. 启动时注册信号处理注册SIGURG→doSigPreempt2. 触发抢占条件GC栈扫描/sysmon检测a. 设置preemptStop=trueb. 调用preemptM3. 发送信号发送SIGURG信号4. 中断线程执行执行doSigPreempt5. 修改执行上下文修改PC寄存器设置返回后执行asyncPreempt6. 执行抢占入口汇编跳转7. 抢占处理调用a. 状态→_Gpreemptedb. 调用schedule()8. 切换协程选择并执行新G运行时runtime.sighandler信号处理runtime.suspendG目标线程操作系统runtime.preemptMdoSigPreemptsigctxt.pushCallasyncPreemptasyncPreempt2preemptPark调度器schedule新Goroutine

基于信号的抢占式调度只解决了垃圾回收和栈扫描时存在的问题,它到目前为止没有解决所有问题,但是这种真抢占式调度是调度器走向成熟的开始

http://www.dtcms.com/a/408998.html

相关文章:

  • 重庆云阳网站建设模板式网站建设
  • 广州网站开发定制公司做网站背景图片怎么放
  • 免费网站的软件下载自建网站管理
  • 爱站网是什么平台做产品设计之前怎么查资料国外网站
  • 网站备案人授权书网站建设中模版
  • 华强北ic网站建设百度站长工具是什么意思
  • 手机有软件做ppt下载网站有哪些内容青岛城阳 软件网站开发
  • 国产手机做系统下载网站燕窝网站怎么做
  • 西宁网站网站建设网站运营策划ppt
  • 清河做网站哪里好长沙网站建设长沙网站制作
  • 网站开发的实验报告制作视频的软件哪个好免费的
  • 腾讯云网站备案流程图企业管理咨询行业很乱
  • o2o与网站建设论文青海网站建设与制作
  • 静态html转化wordpress主题石家庄seo优化
  • 网站建设零金手指专业创建一个网址需要多少钱
  • 小说网站怎么做seo工作室创业项目
  • 淮阴区建设局网站网站建设百强企业
  • 大型做网站wordpress rightlock
  • 衡水城乡建设局网站学it要多久多少学费
  • 资讯网站 怎么做app需要申请网站的子域名吗
  • 免费建网站的服务器国际贸易相关网站
  • 在linux系统上用什么做网站近期国内热点新闻事件
  • 网站开发众包行业网站大全
  • 做网站销售门窗怎么做百度seo规则
  • 广州站在哪里谷德设计网 景观
  • 域名备案的网站建设书赣州做网站的
  • 招商网站大全免费在百度里面做网站要多少钱
  • wap网站 什么意思wordpress 无效用户名
  • 做外贸什么网站无锡 学校网站建设
  • 2021不良正能量免费网站app东莞网站推广公司