一文详解Golang协程调度器scheduler

目录
  • 1. 调度器scheduler的作用
  • 2. GMP模型
  • 3. 调度机制

1. 调度器scheduler的作用

我们都知道,在Go语言中,程序运行的最小单元是gorouines。

然而程序的运行最终都是要交给操作系统来执行的,以Java为例,Java中的一个线程对应的就是操作系统中的线程,以此来实现在操作系统中的运行。在Go中,gorouines比线程更轻量级,其与操作系统的线程也不是一一对应的关系,然而,最终我们想要执行程序,还是要借助操作系统的线程来完成,调度器scheduler的工作就是完成gorouines到操作系统线程的调度。

2. GMP模型

当我们运行go fun(){}时,会生成一个g,优先放置在创建他的p的本地队列中,如果本地队列已满,那么会放置在全局队列中。

g的运行需要借助p与m,p是执行器,只有获得p的g才能执行,p的执行需要挂在m上,m对应的是操作系统中的线程,p的数量与CPU的核数相同。

goroutine运行所需要的上下文信息都是存放在g的数据结构当中的,所以g可以依靠任意的p或者m执行,而对于操作系统而言,其并不能看到p与g的调度过程,这些过程对于操作系统线程来说都是连续的,所以省去了线程上下文切换的开销。

g的数据结构如下所示:

type g struct {
    stack       stack   // g自己的栈

    m            *m      // 执行当前g的m
    sched        gobuf   // 保存了g的现场,goroutine切换时通过它来恢复
    atomicstatus uint32  // g的状态Gidle,Grunnable,Grunning,Gsyscall,Gwaiting,Gdead
    goid         int64
    schedlink    guintptr // 下一个g, g链表

    preempt       bool //抢占标记

    lockedm        muintptr // 锁定的M,g中断恢复指定M执行
    gopc           uintptr  // 创建该goroutine的指令地址
    startpc        uintptr  // goroutine 函数的指令地址
}

p的数据结构如下所示:

type p struct {
    id          int32
    status      uint32 // 状态
    link        puintptr // 下一个P, P链表
    m           muintptr // 拥有这个P的M
    mcache      *mcache  

    // P本地runnable状态的G队列
    runqhead uint32
    runqtail uint32
    runq     [256]guintptr

    runnext guintptr // 一个比runq优先级更高的runnable G

    // 状态为dead的G链表,在获取G时会从这里面获取
    gFree struct {
        gList
        n int32
    }

    gcBgMarkWorker       guintptr // (atomic)
    gcw gcWork

}

m的数据结构如下所示:

type m struct {
    g0      *g     // g0, 每个M都有自己独有的g0

    curg          *g       // 当前正在运行的g
    p             puintptr // 当前用于的p
    nextp         puintptr // 当m被唤醒时,首先拥有这个p
    id            int64
    spinning      bool // 是否处于自旋

    park          note
    alllink       *m // on allm
    schedlink     muintptr // 下一个m, m链表
    mcache        *mcache  // 内存分配
    lockedg       guintptr // 和 G 的lockedm对应
    freelink      *m // on sched.freem

} 

通过gmp模型,我们能解决gorouines到操作系统线程的映射问题,gorouines之间的切换是在用户态完成的,在操作系统的视角来看,线程的上下文切换并不频繁,因此就少了很多陷入内核的过程,所以有更好的并发效果。

3. 调度机制

1)work stealing机制

当一个p上的g执行完之后,他会尝试从其他的p队列中窃取g来执行,以减少操作系统线程的切换动作。

2)hand off机制

这个是针对m来说的,有的时候m可能因为g的信号调用而被操作系统阻塞,这个时候p就会挂载去另一个m继续执行可以执行的g,当阻塞的m就绪之后,会给p发信号,召唤他回来继续进行后续操作。

到此这篇关于一文详解Golang协程调度器scheduler的文章就介绍到这了,更多相关Golang scheduler内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • golang协程池模拟实现群发邮件功能

    比如批量群发邮件的功能 因为发送邮件是个比较耗时的操作, 如果是传统的一个个执行 , 总体耗时比较长 可以使用golang实现一个协程池 , 并行发送邮件 pool包下的pool.go文件 package pool import "log" //具体任务,可以传参可以自定义操作 type Task struct { Args interface{} Do func(interface{})error } //协程的个数 var Nums int //任务通道 var JobChanne

  • golang 40行代码实现通用协程池

    代码仓库 goroutine-pool golang的协程管理 golang协程机制很方便的解决了并发编程的问题,但是协程并不是没有开销的,所以也需要适当限制一下数量. 不使用协程池的代码(示例代码使用chan实现,代码略啰嗦) func (p *converter) upload(bytes [][]byte) ([]string, error) { ch := make(chan struct{}, 4) wg := &sync.WaitGroup{} wg.Add(len(bytes))

  • golang的协程上下文的具体使用

    go协程上下文context golang的context 主要用来在 goroutine 之间传递上下文信息,包括:取消信号.超时时间.截止时间.k-v 等 context是golang1.17版本之后才出的特性 上下文解决的问题 协程间的通信 例如web应用中,每一个请求都由一个协程去处理.当然处理处理请求的这个协程,一般我们还会起一些其他的协程,用来处理其他的业务,比如操作数据库,生份验证.文件读写等.这些协程是独立的,我们在当前的协程中无法感知到其他的协程执行的情况怎么样了.实用通道ch

  • Golang协程池gopool设计与实现

    目录 Goroutine 协程池 gopool 核心实现 Pool Task Worker 整体来看 三个角色的定位 使用 sync.Pool 进行性能优化 Goroutine Goroutine 是 Golang 提供的一种轻量级线程,我们通常称之为「协程」,相比较线程,创建一个协程的成本是很低的.所以你会经常看到 Golang 开发的应用出现上千个协程并发的场景. Goroutine 的优势: 与线程相比,Goroutines 成本很低. 它们的堆栈大小只有几 kb,堆栈可以根据应用程序的需

  • 浅谈golang for 循环中使用协程的问题

    两个例子 package main import ( "fmt" "time" ) func Process1(tasks []string) { for _, task := range tasks { // 启动协程并发处理任务 go func() { fmt.Printf("Worker start process task: %s\n", task) }() } } func main() { tasks := []string{&quo

  • golang实现多协程下载文件(支持断点续传)

    引言 写这篇文章主要是周末休息太无聊,看了看别人代码,发现基本上要么是多协程下载文件要么就只有单协程的断点续传,所以就试了试有进度条的多协程下载文件(支持断点续传) package main import ( "fmt" "io" "os" "regexp" "strconv" "sync" "github.com/qianlnk/pgbar" ) /** * 需求:

  • golang协程与线程区别简要介绍

    目录 一.进程与线程 二.并发与并行 三.go协程与线程 1.调度方式 2.调度策略 3.上下文切换速度 4.栈的大小 四.GMP模型 一.进程与线程 进程是操作系统资源分配的基本单位,是程序运行的实例.例如打开一个浏览器就开启了一个进程. 线程是操作系统调度到CPU中执行的基本单位.例如在浏览器里新建一个窗口就需要一个线程来进行处理. 在一般情况下,线程是进程的组成部分,一个进程可以包含多个线程.例如浏览器可以新建多个窗口. 进程中的多个线程并发执行并共享进程的内存等资源.例如多个窗口之间可以

  • Golang 之协程的用法讲解

    一.Golang 线程和协程的区别 备注:需要区分进程.线程(内核级线程).协程(用户级线程)三个概念. 进程.线程 和 协程 之间概念的区别 对于 进程.线程,都是有内核进行调度,有 CPU 时间片的概念,进行 抢占式调度(有多种调度算法) 对于 协程(用户级线程),这是对内核透明的,也就是系统并不知道有协程的存在,是完全由用户自己的程序进行调度的,因为是由用户程序自己控制,那么就很难像抢占式调度那样做到强制的 CPU 控制权切换到其他进程/线程,通常只能进行 协作式调度,需要协程自己主动把控

  • golang协程设计及调度原理

    目录 一.协程设计-GMP模型 1.工作线程M 2.逻辑处理器p 3.协程g 4.全局调度信息schedt 5.GMP详细示图 二.协程调度 1.调度策略 获取本地运行队列 获取全局运行队列 协程窃取 2.调度时机 主动调度 被动调度 抢占调度 一.协程设计-GMP模型 线程是操作系统调度到CPU中执行的基本单位,多线程总是交替式地抢占CPU的时间片,线程在上下文的切换过程中需要经过操作系统用户态与内核态的切换.golang的协程(G)依然运行在工作线程(M)之上,但是借助语言的调度器,协程只需

  • golang协程池设计详解

    Why Pool go自从出生就身带"高并发"的标签,其并发编程就是由groutine实现的,因其消耗资源低,性能高效,开发成本低的特性而被广泛应用到各种场景,例如服务端开发中使用的HTTP服务,在golang net/http包中,每一个被监听到的tcp链接都是由一个groutine去完成处理其上下文的,由此使得其拥有极其优秀的并发量吞吐量 for { // 监听tcp rw, e := l.Accept() if e != nil { ....... } tempDelay = 0

随机推荐