go语言需要学习的技术记录

  1. 协程原理,理解协程开发思路
  2. 从通信与共享内存角度,了解channel原理
  3. 精通go网络通信原理,提升架构思维
  4. 研究go堆内存结构,优化GC性能

下载 go语言环境

gomirrors.org

认识 go语言

  • go语言的特点
  • go语言的优势
  • go语言的途径

相对于c语言来说,c语言不是面向对象,直接编译为机器码,不需要执行环境 ,c语言的一次编码只能适用于一种平台,自己处理GC问题,go语言 可以直接编译为二进制,没有虚拟化损失,自带运行环境,无需处理Gc问题。

优秀的go语言项目:

  1. docker
  2. etcd
  3. kubernetes
  4. codis (类似redis的)
  5. falcon (系统监测)
  6. tidb (关系型数据库)
  7. beego

go 语言特性

  • go没有对象、没有类、没有继承
  • 通过组合匿名字段达到类似继承的效果
  • 去掉了面向对象中复杂冗余的部分
  • 保留基本的 面向对象的特性

go包管理问题

  • 没有统一的包管理方式
  • 包之间的依赖关系很难维护
  • 如果同时需要一个包的不同版本,非常麻烦
  • 解决方式:
    • godep
    • govendor
    • glide
    • 以上方法的问题是,未彻底解决GOPATH存在的问题,使用起来麻烦

go11 有一个 go modules 用来管理go的包

  • 本质上,go包是一个项目的 源码,
  • gomod 作用是,将 go包和git项目关联起来
  • go包的版本就是 git 项目的 tag
  • gomod 解决了 需要哪个git项目的 什么版本
1
2
3

go get xxx@v1
# 我们可以直接 go get 下载就可以了

golang runtime的特点

  • 没有虚拟机的概念
  • runtime作为程序的一部分打包二进制产物 (runtime负责内存管理、垃圾回收、协程调度,并且把runtime直接一起打包到二进制里面)
  • 内存管理能力
  • 垃圾回收能力 (GC)
  • 并发能力 (协程调度)
  • runtime屏蔽系统调用的能力
  • 一些go的关键字其实是 runtime下的函数
关键字 函数
go newproc
new newobject
make makeslice,makechain,makemap
<- chansend1,chanrecv1

go程序编译

1
2
go build -n 
# build -n 表示不实际编译,只是输出编译过程

go语言编译过程

graph LR 词法分析 --> 句法分析 -->语义分析-->中间代码(SSA)生成-->代码优化-->机器码生成-->链接

查看总结代码生成的过程

1
2
3
4
$env:GOSSAFUNC="main"

go build
# dumped ssa to html

go程序是如何启动起来的?

go程序的入口

查看 汇编的入口程序

src/runtime/asm_amd64.s

初始化栈 ,将 (参数数量)argc, (参数值)argv 拷贝到栈上

初始化 g0执行栈

  • g0 是为了调度协程而产生的协程
  • g0是每个go程序的第一个协程(母协程)
  • runtime.check (汇编里调用的)
    • 检查各种类型 长度
    • 检查结构体字段偏移量
    • 检查cas操作
    • 检查指针操作
    • 检查 atomic 原子操作
    • 检查栈大小是否是2的幂
  • CALL runtime·args(SB)
  • CALL runtime·osinit(SB)
  • CALL runtime·schedinit(SB)
    • 初始化调度器
1
2
3
4

	CALL	runtime·check(SB)

	MOVL	24(SP), AX		/
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
	// copy arguments forward on an even stack
	MOVQ	DI, AX		// argc
	MOVQ	SI, BX		// argv
	SUBQ	$(5*8), SP		// 3args 2auto
	ANDQ	$~15, SP
	MOVQ	AX, 24(SP)
	MOVQ	BX, 32(SP)

	// create istack out of the given (operating system) stack.
	// _cgo_init may update stackguard.
	MOVQ	$runtime·g0(SB), DI
	LEAQ	(-64*1024+104)(SP), BX
	MOVQ	BX, g_stackguard0(DI)
	MOVQ	BX, g_stackguard1(DI)
	MOVQ	BX, (g_stack+stack_lo)(DI)
	MOVQ	SP, (g_stack+stack_hi)(DI)
 

调度器初始化 runtime.schedinit

  • 全局栈空间内存分配
  • 堆内存空间初始化
  • 初始化当前系统线程
  • 算法初始化 (map、hash)
  • 加载命令行参数 os.Args
  • 加载操作系统环境变量
  • 垃圾回收器参数初始化
  • 设置 process 数量
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20

MOVL	24(SP), AX		// copy argc
MOVL	AX, 0(SP)
MOVQ	32(SP), AX		// copy argv
MOVQ	AX, 8(SP)
CALL	runtime·args(SB)
CALL	runtime·osinit(SB)
CALL	runtime·schedinit(SB)

// create a new goroutine to start program
MOVQ	$runtime·mainPC(SB), AX		// entry
PUSHQ	AX
CALL	runtime·newproc(SB)
POPQ	AX

// start this MM是线程,调度器的M
CALL	runtime·mstart(SB)

CALL	runtime·abort(SB)	// mstart should never return
RET

执行顺序:

  • 创建新的协程,执行 runtime.main
  • 放入调度器等待调度
  • 初始化一个 M,用来调度主协程 runtime.mstart

看go源码 runtime/proc.go 的main函数下面,这个才是 golang 真实执行的 main函数。

  • GO启动经历了检查、各种初始化、初始化协程调度的过程
  • 用户 main.go下面的的 main 函数 也是在协程中运行的
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135

// The main goroutine.
func main() {
	g := getg()

	// Racectx of m0->g0 is used only as the parent of the main goroutine.
	// It must not be used for anything else.
	g.m.g0.racectx = 0

	// Max stack size is 1 GB on 64-bit, 250 MB on 32-bit.
	// Using decimal instead of binary GB and MB because
	// they look nicer in the stack overflow failure message.
	if goarch.PtrSize == 8 {
		maxstacksize = 1000000000
	} else {
		maxstacksize = 250000000
	}

	// An upper limit for max stack size. Used to avoid random crashes
	// after calling SetMaxStack and trying to allocate a stack that is too big,
	// since stackalloc works with 32-bit sizes.
	maxstackceiling = 2 * maxstacksize

	// Allow newproc to start new Ms.
	mainStarted = true

	if GOARCH != "wasm" { // no threads on wasm yet, so no sysmon
		systemstack(func() {
			newm(sysmon, nil, -1)
		})
	}

	// Lock the main goroutine onto this, the main OS thread,
	// during initialization. Most programs won't care, but a few
	// do require certain calls to be made by the main thread.
	// Those can arrange for main.main to run in the main thread
	// by calling runtime.LockOSThread during initialization
	// to preserve the lock.
	lockOSThread()

	if g.m != &m0 {
		throw("runtime.main not on m0")
	}

	// Record when the world started.
	// Must be before doInit for tracing init.
	runtimeInitTime = nanotime()
	if runtimeInitTime == 0 {
		throw("nanotime returning zero")
	}

	if debug.inittrace != 0 {
		inittrace.id = getg().goid
		inittrace.active = true
	}

	doInit(&runtime_inittask) // Must be before defer.

	// Defer unlock so that runtime.Goexit during init does the unlock too.
	needUnlock := true
	defer func() {
		if needUnlock {
			unlockOSThread()
		}
	}()
    //开启垃圾回收功能
	gcenable()

	main_init_done = make(chan bool)
	if iscgo {
		if _cgo_thread_start == nil {
			throw("_cgo_thread_start missing")
		}
		if GOOS != "windows" {
			if _cgo_setenv == nil {
				throw("_cgo_setenv missing")
			}
			if _cgo_unsetenv == nil {
				throw("_cgo_unsetenv missing")
			}
		}
		if _cgo_notify_runtime_init_done == nil {
			throw("_cgo_notify_runtime_init_done missing")
		}
		// Start the template thread in case we enter Go from
		// a C-created thread and need to create a new thread.
		startTemplateThread()
		cgocall(_cgo_notify_runtime_init_done, nil)
	}

	doInit(&main_inittask)

	// Disable init tracing after main init done to avoid overhead
	// of collecting statistics in malloc and newproc
	inittrace.active = false

	close(main_init_done)

	needUnlock = false
	unlockOSThread()

	if isarchive || islibrary {
		// A program compiled with -buildmode=c-archive or c-shared
		// has a main, but it is not executed.
		return
	}
	fn := main_main // make an indirect call, as the linker doesn't know the address of the main package when laying down the runtime
	fn() //用户 main.go main 方法
	if raceenabled {
		racefini()
	}

	// Make racy client program work: if panicking on
	// another goroutine at the same time as main returns,
	// let the other goroutine finish printing the panic trace.
	// Once it does, it will exit. See issues 3934 and 20018.
	if atomic.Load(&runningPanicDefers) != 0 {
		// Running deferred functions should not take long.
		for c := 0; c < 1000; c++ {
			if atomic.Load(&runningPanicDefers) == 0 {
				break
			}
			Gosched()
		}
	}
	if atomic.Load(&panicking) != 0 {
		gopark(nil, nil, waitReasonPanicWait, traceEvGoStop, 1)
	}

	exit(0)
	for {
		var x *int32
		*x = 0
	}
}