go语言中defer按后进先出(lifo)顺序执行,因每个defer语句在执行时即压入函数私有栈,函数返回时从栈顶依次弹出执行;参数在defer注册时求值,非执行时取值。

多个 defer 为什么不是按代码顺序执行
Go 的 defer 不是“写在前面就先执行”,而是压入函数私有栈,函数返回时统一从栈顶弹出——也就是后进先出(LIFO)。你看到 defer f1() 写在上面、defer f2() 写在下面,实际执行顺序是 f2() 先于 f1()。
常见错误现象:os.Open 后紧接着 bufio.NewReader,然后按“先开后关”直觉写成:
defer file.Close() defer reader.Close()
结果 reader.Close() 先执行,但 reader 依赖 file,file 已关导致 panic:“invalid argument” 或 “use of closed file”。
- 执行顺序只取决于
defer语句在源码中**出现的先后位置**,与缩进、是否在if或for块内无关 - 只要该
defer语句被执行到(没被提前return或panic跳过),就会入栈 -
defer func(){}()是立即调用,不是延迟;真正延迟的是defer func(){}()中的func(){},但加了括号就变成立即执行表达式
defer 参数在注册时就求值,不是执行时
defer fmt.Println(i) 中的 i 是这条 defer 语句执行那一刻的值快照,不是函数退出时的值。循环里直接这么写,几乎必然出错。
典型错误场景:
for i := 0; i <p>输出是 <code>2</code>、<code>1</code>、<code>0</code>(LIFO + 快照),而不是你以为的 <code>0</code>、<code>1</code>、<code>2</code> 或三个 <code>2</code>。</p><div class="aritcle_card flexRow artxards"> <div class="artcardd flexRow"> <a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill3927" title="Colly Golang Web Scraper and Crawler Framework"><img src="https://img.php.cn/upload/skill/000/000/081/178986975225346.jpg" alt="Colly Golang Web Scraper and Crawler Framework" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a> <div class="aritcle_card_info flexColumn"> <a rel="nofollow" href="/xiazai/skill3927" title="Colly Golang Web Scraper and Crawler Framework" class="overflowclass">Colly Golang Web Scraper and Crawler Framework</a> <p class="overflowclass">Colly 是一个用于 Go 语言的快速开源爬取和爬虫框架。它适用于从简单的页面提取到异步爬虫处理大量页面集合,支持请求回调和结构化解析。</p> </div> <a rel="nofollow" href="/xiazai/skill3927" title="Colly Golang Web Scraper and Crawler Framework" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span> </a> </div> </div>
- 值类型(如
int、string)拷贝的是当时值 - 指针或结构体字段若被后续修改,
defer执行时看到的是修改后的状态(因为地址没变) - 要捕获当前循环变量值,得用闭包传参:
defer func(x int) { fmt.Println(x) }(i)
资源释放必须严格遵循“后开先关”逆序
嵌套资源(文件→缓冲读取器、数据库连接→事务、锁→goroutine)的关闭顺序,必须和创建顺序相反。这不是风格问题,是避免 invalid state 的硬性要求。
例如:
- 先
db.Begin(),再tx.Query()→defer tx.Rollback()必须在defer db.Close()之前注册 - 先
mu.Lock(),再启 goroutine 处理 →defer mu.Unlock()必须确保 goroutine 已结束,否则可能 race;更稳妥是把 unlock 放在 goroutine 内部或用sync.Once - HTTP handler 中
defer resp.Body.Close()要放在http.Get之后立刻注册,不能等所有逻辑跑完才写,否则中间 panic 会导致泄露
panic 时 defer 仍执行,recover 必须在 defer 内
defer 在 panic 发生后依然会执行,这是 Go 错误恢复机制的基础。但 recover() 只有在 defer 函数内部调用才有效。
错误写法:
func f() {
recover() // 这个永远不生效
panic("boom")
}
正确写法:
func f() {
defer func() {
if r := recover(); r != nil {
log.Printf("recovered: %v", r)
}
}()
panic("boom")
}
-
recover()只对同 goroutine 中、且尚未传播出去的 panic 有效 - 多个
defer都含recover()时,只有最靠近 panic 的那个(即最后注册的)能捕获,其余看到的是已 recover 状态 - 不要在 defer 外层 try-catch 式包裹 recover,它只在 defer 函数体内有意义
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










