go语言 - Scheduler原理以及查看Goroutine执行

幸运草 2020年4月22日21:59:03前端框架评论阅读模式

最近看了看go scheduler的基本原理,本文介绍go语言scheduler的基本原理以及如何查看go代码中的go routine的执行情况。

0)Scheduler(调度器)

熟悉go语言的小伙伴应该都使用过goroutine。goroutine就是Go语言提供的一种用户态线程。Scheduler是调度goroutine的调度器。

go语言 - Scheduler原理以及查看Goroutine执行

Go的调度器内部有三个重要概念:M,P,G。
M (machine): 代表真正的内核操作系统里面的线程,和POSIX里的thread差不多,也是真正执行goroutine逻辑的部分。
G (Goroutine): 代表一个goroutine。
P (Processor): 代表调度的上下文,可以理解成一个局部调度器。

Go语言实现了多个Goroutine到多个Processor的映射(调度)。注意的是,针对X个Processor,Scheduler可能创建多于X个M(有些M可能会暂时被block)。还需要理解额外两个概念:GRQ(Global Running Queue)以及 LRQ(Local Running Queue)。未指定Processor的Goroutine会存放在GRQ上,在调度到合适的Processor后,会将一个Goroutine从GRQ移动到LRQ。

Go程序中发生了四类事件,允许调程序做出调度决策。

a. 使用关键字go b. 垃圾收集 c. 系统调用 d. 同步

1)Processor的个数

Processor的个数可以通过GOMAXPROCS环境变量设置。GOMAXPROCS默认值是CPU的核数。Processor的个数可以通过如下的go代码进行查询:

package main

import (

"fmt"

"runtime"

)

func main() {

// NumCPU returns the number of logical

// CPUs usable by the current process.

fmt.Println(runtime.NumCPU())

}

也就是通过runtime.NumCPU函数可以获得Processor的个数。查看go语言的源代码(runtime/os_linux.c),NumCPU函数的实现函数如下:

 func getproccount() int32 {

const maxCPUs = 64 * 1024

var buf [maxCPUs / 8]byte

r := sched_getaffinity(0, unsafe.Sizeof(buf), &buf[0])

if r < 0 {

return 1

}

n := int32(0)

for _, v := range buf[:r] {

for v != 0 {

n += int32(v & 1)

v >>= 1

}

}

if n == 0 {

n = 1

}

return n

}

在linux操作系统中,通过调用sched_getaffinity函数获取cpu_set_t的信息,从而推算出CPU的个数。

2)查看Processor以及Goroutine的调度情况

在go程序命令行加上GODEBUG=schedtrace=1000,scheddetail=1选项,可以实时打印Goroutine的调度情况:schedtrace参数指定打印的时间间隔,scheddetail参数指定是否打印更多细节。

以下面的代码为例:

package main

import "fmt"

import "time"

func f(from string, index int) {

fmt.Println("index: ", index)

var i int=0

for true {

i  = i + 1

//time.Sleep(1 * time.Second)

}

}

func main() {

var i int= 0

for true {

go f("goroutine", i)

i+=1

time.Sleep(1 * time.Second)

}

}

这段go程序,不停的创建Goroutine,每个Goroutine执行f函数。注意,f函数中也是一个for循环(默认的情况下Sleep函数不起作用)。

如果只用schedtrace=1000参数,在MAC笔记本上的输出结果如下:

index:  0

index:  1

SCHED 0ms: gomaxprocs=4 idleprocs=2 threads=5 spinningthreads=0 idlethreads=1 runqueue=0 [0 0 0 0]

index:  2

SCHED 1009ms: gomaxprocs=4 idleprocs=1 threads=6 spinningthreads=0 idlethreads=1 runqueue=0 [0 0 0 0]

index:  3

SCHED 2019ms: gomaxprocs=4 idleprocs=0 threads=6 spinningthreads=0 idlethreads=0 runqueue=0 [0 0 0 0]

SCHED 3020ms: gomaxprocs=4 idleprocs=0 threads=6 spinningthreads=0 idlethreads=1 runqueue=1 [0 0 0 0]

SCHED 4021ms: gomaxprocs=4 idleprocs=0 threads=6 spinningthreads=0 idlethreads=1 runqueue=1 [0 0 0 0]

SCHED 5030ms: gomaxprocs=4 idleprocs=0 threads=6 spinningthreads=0 idlethreads=1 runqueue=1 [0 0 0 0]

一行SCHED开头的log打印出了当前时刻Scheduler的状态:

gomaxprocs:多少个Processor

idleProcs:多少个空闲的Processor

threads:多少个M

idlethreads:多少个空闲thread

runqueue:GRQ以及LRQ的状态,第一个数字是GRQ中的Goroutine的数量,[ ] 中的数字是各个LRQ中的Goroutine的数量。

你会发现上述的go代码中只跑了4个Goroutine,不会创建新的Goroutine,而且GRQ中一直有个Goroutine永远调度不到。原因是f函数是个死循环,不会释放Processor。

如果在f函数中加入延时,则会不停的创建以及调度Goroutine。

如果使用scheddetail=1,会打印出具体的Thread的执行信息。

总结:goroutine就是Go语言提供的一种用户态线程。Scheduler是调度goroutine的调度器。Go的调度器内部由三部分组成:M(Machine),P(Processor),G(Goroutine)。在go程序命令行加上GODEBUG=schedtrace=1000,scheddetail=1选项,可以实时打印Goroutine的调度情况。

特别声明:以上文章内容仅代表作者本人观点,不代表变化吧观点或立场。如有关于作品内容、版权或其它问题请于作品发表后的30日内与变化吧联系。

  • 赞助本站
  • 微信扫一扫
  • weinxin
  • 加入Q群
  • QQ扫一扫
  • weinxin
幸运草
Go语言接口规则 前端框架

Go语言接口规则

Go语言接口规则 接口是一个或多个方法签名的集合。任何类型的方法集中只要拥有该接口对应的全部方法签名。就表示它 "实现" 了该接口,无须在该类型上显式声明实现了哪个接口。对应方法,是指有相同名称、参数...
Go语言中处理 HTTP 服务器 前端框架

Go语言中处理 HTTP 服务器

1 概述 包 net/http 提供了HTTP服务器端和客户端的实现。本文说明关于服务器端的部分。 快速开始: package main import (   "log"   "net/http" )...
Go语言常见排序算法 前端框架

Go语言常见排序算法

Go语言常见排序算法 冒泡排序 思路分析:在要排序的切片中,对当前还未排好的序列,从前往后对相邻的两个元素依次进行比较和调整,让较大的数往下沉,较小的往上冒。即,每当两相邻的元素比较后发现它们的排序与...

发表评论