Go函数调用顺序由执行流决定而非声明顺序,main()为入口,init()在main前按包导入和文件字典序执行,goroutine启动不保证执行顺序,defer按后进先出且参数在声明时求值。

函数调用顺序由执行流决定,不是声明顺序
Go 程序从 main() 函数开始执行,函数调用严格按代码中实际出现的顺序(即控制流路径)发生。变量声明位置、函数定义顺序、甚至 init() 函数的存在,都不改变运行时调用链——只影响初始化时机。
常见误解是“先定义的函数会先被调用”,其实完全不成立。比如下面这段代码:
package main
import "fmt"
func second() { fmt.Println("second") }
func first() { fmt.Println("first") }
func third() { fmt.Println("third") }
func main() {
first()
second()
third()
}
输出一定是 first → second → third,和函数定义顺序无关。
init() 函数在 main() 之前自动执行,但仅限包级
每个 Go 源文件可含多个 init() 函数,它们在 main() 运行前按**包导入顺序 + 文件字典序**执行,且每个 init() 内部语句仍按书写顺序执行。
立即学习“go语言免费学习笔记(深入)”;
-
init()不可被显式调用,也不接受参数或返回值 - 同一文件内多个
init()是语法错误;不同文件可以有多个 - 如果包 A 导入包 B,B 的
init()一定在 A 的init()之前完成 - 所有
init()执行完,才进入main()
示例中若 utils/utils.go 有 init(){ fmt.Print("utils ") },而 main.go 导入它,则输出必为 utils main。
goroutine 启动不等于立即执行,调度由 runtime 控制
写 go f() 只是向调度器提交一个任务,f() 的实际执行时间不可预测。这直接影响你对“调用顺序”的理解——它不再是线性时间轴上的确定序列。
- 主 goroutine 中连续启动两个 goroutine:
go a()和go b(),不能保证a()先于b()开始运行 - 若需顺序执行,必须用同步机制(如
sync.WaitGroup、channel 或 mutex)显式约束 - 没有同步时,
fmt.Println("done")可能在a()和b()任意一个甚至都未开始前就输出
别依赖 goroutine 启动顺序做逻辑判断,这是并发 bug 的高发区。
defer 语句的执行顺序是后进先出(LIFO),且在函数 return 后、实际返回前
defer 不改变函数体内的执行顺序,但它注册的调用会在函数退出时逆序执行——这点极易误判。
- 所有
defer在函数 return 语句执行时才被登记,但参数在 defer 语句出现时就求值(除非是闭包引用) - return 后的 defer 才真正执行,所以
defer fmt.Println(x)中的x是 return 时刻的值,不是 defer 语句处的值(若 x 是局部变量且后续被修改) - 多个 defer 嵌套时,最晚写的 defer 最先执行
例如:
func f() {
x := 1
defer fmt.Println(x) // 输出 1,因为 x 此时是 1
x = 2
return
}
而闭包形式会捕获变量本身:
func g() {
x := 1
defer func() { fmt.Println(x) }() // 输出 2,因为闭包读取的是 return 时的 x
x = 2
return
}
实际调试时,最容易忽略 defer 参数求值时机与执行时机的分离,导致日志或清理逻辑不符合预期。










