-10 +

Golang scheduler

介绍 Golang 的并发模型

我们都知道 Golang 是在语言级别支持并发的,通过关键字 go 就可以创建一个并发的协程。这篇 blog 主要是试图讲解清楚使用 go 关键字时, 都发生了什么。

Golang 处理这里 go 关键字的内部主要使用到了四个相关的概念:

我们来看一张图,就可以清楚的知道 M, P, G 的关系:

地鼠(gopher)用小车运着一堆待加工的砖。M 就可以看作图中的地鼠,P就是小车,G就是小车里装的砖,一图胜千言。

go 关键字详解

上面我们清楚了 Golang 内部是怎么处理 M, P, G 的,这小节我们来看看 G 是怎么来,也就是使用 go 关键字后都发生了什么, 首相我们会使用一个简单的 go 使用的例子:

// test.go  

package main 

import () 

func add(x, y int) int { 
  z := x + y   
  return z 
} 

func main() { 
  x := 0x100 
  y := 0x200

  go add(x, y)

} 

为了演示我们使用了一个非常简单的例子,现在我们来看看编译的代码是什么情况:

$ go build -o test test.go 

$ go tool objdump -s "main\.main" test 

TEXT main.main(SB) test.go 

    test.go:10   SUBQ $0x28, SP 
    test.go:11   MOVQ $0x100, CX
    test.go:12   MOVQ $0x200, AX 
    test.go:13   MOVQ CX, 0x10(SP)          //  x 入栈 
    test.go:13   MOVQ AX, 0x18(SP)          //  y 入栈
    test.go:13   MOVL $0x18, 0(SP)          //  参数长度入栈
    test.go:13   LEAQ 0x879ff(IP), AX       //  add 地址放入 AX 
    test.go:13   MOVQ AX, 0x8(SP)           //  地址如栈
    test.go:13   CALL runtime.newproc(SB) 
    test.go:14   ADDQ $0x28, SP 
    test.go:14   RET 

中上面的汇编可以看到,会调用 runtime.newproc,函数如下:

// runtime/proc1.go

func newproc(siz int32, fn *funcval) {
	argp := add(unsafe.Pointer(&fn), ptrSize)
	pc := getcallerpc(unsafe.Pointer(&siz))
	systemstack(func() {
		newproc1(fn, (*uint8)(argp), siz, 0, pc)
	})
}

我们从汇编代码可以看到入栈了 4 个参数,但是 newproc 函数只有 2 个参数,这是因为使用的了 fn *funcval 将后面的参数都捏一起,组成一个参数。 我们讲接下上面代码的意思,可以对照下面的内存布局图:

这样我们就清楚了 go 关键字后,具体在内存中发生了什么。

调度算法介绍

调度核心的算法可以用下图来说明:

参考

关于我

85 后程序员, 比较熟悉 Java,JVM,Golang 相关技术栈, 关注 Liunx kernel,目前痴迷于分布式系统的设计和实践。 研究包括但不限于 Docker Kubernetes eBPF 等相关技术。

Blog

Code

Life

Archive