0
点赞
收藏
分享

微信扫一扫

Go语言学习笔记

七千22 2022-02-08 阅读 177

目录

GPM模型(Go调度器模型)

三者关系,具体过程

系统调用阻塞

sysmon


 

 


GPM模型(Go调度器模型)

  • Goroutine

goroutine是用go关键字创建的协程,它由编程层面实现,每个goroutine对应一个runtime包的结构体G,里面存有goroutine的执行栈、状态、当前占用的线程、调度相关的数据等,

G本身不是执行体,每个G都必须绑定到GPM里的P上才能运行。

  • Processor

processor负责Machine与Goroutine的连接,它提供线程的执行环境,比如内存分配状态、结构体G的任务队列等。P的数量是按照GOMAXPROCS来设置的,与线程数量一一对应。

  • Machine

抽象地表示操作系统的线程。这里最多会有GOMAXPROCS(环境变量)个线程,默认情况下GOMAXPROCS设置成了cpu内核数。

M它是一个runtime.m结构体,每一个线程对应一个runtime.m结构体。这个结构体里存了:

  • g0:会深度参与运行时的调度过程,比如goroutine的创建、内存分配等
  • curg:代表当前正在线程上的goroutine

还存了和P有关的一些数据

  • p:正在运行代码的处理器
  • nextp:暂存的处理器
  • oldp:系统调用之前的线程的处理器
type m struct{
g0 *g
curg *g
...
p puintptr
nextp puintptr
oldp puintptr
}

它绑定了P以后进入循环,不停地从P里的队列和全局的队列里拿G,切换到G的执行栈上执行G的任务函数,结束后用goexit做清理工作回到M。

三者关系,具体过程

        首先,go根据机器的逻辑cpu个数创建P和M,然后一个Goroutine结构体被创建后,它会进到一个处理器P的队列等待被M取出执行,如果所有处理器P的私有队列都满了,那么接下来的G就会被放到一个全局的等待队列。

这边M也会不停地从队列里取,对应的P的队列取完了就去全局队列取,全局队列也没有了就会去别的P的队列取。

如果队列都空了,M与P的连接就会断开

当两个goroutine在channel通信时阻塞住了,对应的M就会闲下来转而向别的G服务

系统调用阻塞

当正在运行的goroutine需要执行一个阻塞的系统调用,如读写文件,线程和goroutine会从逻辑处理器P上分离,线程会一直阻塞直到系统调用返回。

此时P会从本地队列选取另一个groutine来运行。而当系统调用执行完成并返回后,之前的groutine会尝试以下行为:

  • 试图获取之前原来的P,如果获取到,则恢复执行
  • 试图在空闲列表中获取一个P,如果获取到,则恢复执行
  • 将协程goroutine放入全局队列中,将相关的M放入空闲列表中

并且,Go对非阻塞 I/O (例如http调用)这种资源尚未就绪的情况也做了处理。在这种情况时,系统调用会遵循以上的工作流,但是会因为资源尚未就绪导致失败,然后Go会强制使用network poller来停放goroutine

sysmon

参考

除此之外,还有一个监控线程:sysmon,作为一个结构体M,它不需要P就可要独立运行,它专门用于监控程序帮助解决程序可能遇到的瓶颈的线程,sysmon并没有链接到GMP模型中的任意P上,所以不会被调度器调度,因此会始终处于运行状态。

该线程作用广泛并涉及以下方面:

  • 由应用程序创建的计时器(timers)。sysmon查看应该在运行但仍在等待的计时器。在这种情况下,Go将查看空闲的M和P列表以尽快运行它们。
  • 网络轮询器(net poller )和系统调用( system calls)。它运行网络操作中阻塞的goroutines。
  • 如果垃圾收集器(garbage collector)已经很长时间没有运行(超过2分钟),sysmon将强制一轮垃圾回收。
  • 长时间运行goroutine的抢占。任何运行超过10毫秒的goroutine将会被抢占把运行时间留给其他goroutines。

sysmon的活动频率是动态的,如果它一直无事可做会加快活动频率,而当以外2种情况下它会进入休眠并且不会消耗任何资源。

  • 垃圾收集器将要运行时(sysmon将会在回收结束后恢复运行)
  • 所有线程都处于空闲时
举报

相关推荐

0 条评论