Go程序启动流程
大体流程主要是:
编译 -> 加载准备 (初始化环境:内存分配器,调度器,垃圾回收器等模块)
-> 运行 (调用main函数) -> 程序退出(资源释放,关闭协程等)。
源代码调试
func main() {
fmt.Println("hello world")
}
- 首先编译源代码GOFLAGS="-ldflags=-compressdwarf=false" go build -gcflags "-N -l" main.go
Mac上的交叉编译,可以使用GOFLAGS="-ldflags=-compressdwarf=false" GOOS=linux GOARCH=arm64 go build -gcflags "-N -l" main.go。 - 安装调试工具gdb
- gdb ./main 调试可执行文件。
172: // just before we're about to start letting user code run.
173: // It kicks off the background sweeper goroutine, the background
174: // scavenger goroutine, and enables GC.
175: func gcenable() {
176: // Kick off sweeping and scavenging.
=> 177: c := make(chan int, 2)
178: go bgsweep(c)
179: go bgscavenge(c)
180: <-c
181: <-c
182: memstats.enablegc = true // now that runtime is initialized, GC is okay
(dlv) goroutines
* Goroutine 1 - User: /Users/cotox/go1.20/src/runtime/mgc.go:177 runtime.gcenable (0x104076138) (thread 3776358)
Goroutine 2 - User: /Users/cotox/go1.20/src/runtime/proc.go:382 runtime.gopark (0x1040944c4) [force gc (idle)]
[2 goroutines]
175: func gcenable() {
176: // Kick off sweeping and scavenging.
177: c := make(chan int, 2)
178: go bgsweep(c)
=> 179: go bgscavenge(c)
180: <-c
181: <-c
182: memstats.enablegc = true // now that runtime is initialized, GC is okay
183: }
184:
(dlv) goroutines
* Goroutine 1 - User: /Users/cotox/go1.20/src/runtime/mgc.go:179 runtime.gcenable (0x10407618c) (thread 3776358)
Goroutine 2 - User: /Users/cotox/go1.20/src/runtime/proc.go:382 runtime.gopark (0x1040944c4) [force gc (idle)]
Goroutine 17 - User: /Users/cotox/go1.20/src/runtime/proc.go:382 runtime.gopark (0x1040944c4) [GC sweep wait]
[3 goroutines]
=> 182: memstats.enablegc = true // now that runtime is initialized, GC is okay
183: }
184:
185: // Garbage collector phase.
186: // Indicates to write barrier and synchronization task to perform.
187: var gcphase uint32
(dlv) goroutines
* Goroutine 1 - User: /Users/cotox/go1.20/src/runtime/mgc.go:182 runtime.gcenable (0x1040761e4) (thread 3776358)
Goroutine 2 - User: /Users/cotox/go1.20/src/runtime/proc.go:382 runtime.gopark (0x1040944c4) [force gc (idle)]
Goroutine 17 - User: /Users/cotox/go1.20/src/runtime/proc.go:382 runtime.gopark (0x1040944c4) [GC sweep wait]
Goroutine 18 - User: /Users/cotox/go1.20/src/runtime/proc.go:382 runtime.gopark (0x1040944c4) [GC scavenge wait]
[4 goroutines]
GC的启动,是在执行runtime.main函数时候,通过方法gcenable(),分别使用go bgsweep(c)和go bgscavenge(c)启动GC运行。
并且在runtime.main里调用用户的main函数入口执行。通过代码:
249: fn := main_main // make an indirect call, as the linker doesn't know the address of the main package when laying down the runtime
=> 250: fn()
m0是什么,有什么作用
- m0 是Go Runtime创建的第一个系统线程,也就是主线程
- 声明方式和其他m线程一样( 普通的m线程是调度器P将可执行g调度给工作线程)
- m0会调度执行g0
goroutine的类型
- 启动时执行调度任务的叫g0(系统线程)
- 执行用户任务的叫做g
- 执行runtime.main的main goroutine(第一个用户级协程)
在Go语言中,g0(也称为Goroutine 0)是一个特殊的Go协程(goroutine),它在程序启动时创建,并且在程序的整个生命周期内一直存在。
g0有一些独特的特性和作用:
- 启动和初始化:g0负责启动程序的执行,包括调用runtime.main函数,这是程序的入口点。
- 系统调度:g0不参与正常的Go协程调度。它主要用于执行系统级的调度任务,如垃圾回收(GC)。
- 垃圾回收:g0在执行垃圾回收时扮演重要角色。它不会执行用户代码,但在GC过程中,它会暂停其他协程的执行。
- 程序退出:g0还负责程序的退出流程。当程序结束时,g0会执行清理工作,包括关闭所有打开的资源和退出程序。
执行runtime.main函数的goroutine是程序启动时创建的第一个用户级别的协程。
它是从g0中启动的,但它与g0有以下区别:
- 用户代码执行:执行runtime.main的goroutine负责执行用户的初始化代码和主函数。
- 调度:这个goroutine可以被调度器挂起和恢复,与普通用户协程一样。
- 生命周期:执行runtime.main的goroutine的生命周期与程序的执行周期相同,但它不是g0。当程序结束时,这个goroutine会结束执行。
- 并发:尽管runtime.main是程序的入口点,但用户可以在程序中创建其他协程来实现并发。
总结来说,g0和执行runtime.main的goroutine不是同一个。g0是一个特殊的系统协程,负责启动程序和执行系统任务,而执行runtime.main的goroutine是程序的第一个用户级别的协程,负责执行用户的初始化代码和主逻辑。两者在程序中扮演不同的角色,并有不同的生命周期和行为
g0和普通goroutine的区别?
- 栈分配区别: g0是系统栈上分配systemstack,linux默认8M,常规g runtime上栈默认2kb,可以扩缩容)
- 功能上区别: g0运行在操作系统线程上,主要执行协程调度;每个m只有一个g0,只绑定一个g0; 常规g执行用户任务,被等待调度。
- 执行流程上区别:g0固定的调度执行逻辑;g是根据任务代码逻辑执行不同。