上卷 程序设计
中卷 标准库
- bufio 1.18
- bytes 1.18
- io 1.18
- container 1.18
- encoding 1.18
- crypto 1.18
- hash 1.18
- index 1.18
- sort 1.18
- context 1.18
- database 1.18
- connection
- query
- queryrow
- exec
- prepare
- transaction
- scan & null
- context
- tcp
- udp
- http
- server
- handler
- client
- h2、tls
- url
- rpc
- exec
- signal
- embed 1.18
- plugin 1.18
- reflect 1.18
- runtime 1.18
- KeepAlived
- ReadMemStats
- SetFinalizer
- Stack
- sync 1.18
- atomic
- mutex
- rwmutex
- waitgroup
- cond
- once
- map
- pool
- copycheck
- nocopy
- unsafe 1.18
- fmt 1.18
- log 1.18
- math 1.18
- time 1.18
- timer
下卷 运行时
源码剖析
附录
文章来源于网络收集而来,版权归原创者所有,如有侵权请及时联系!
4.2 延迟调用
语句 defer
注册 稍后执行的函数调用。这些调用被称作 延迟调用 ,因为它们直到当前函数结束前( RET
)才被执行。常用于资源释放、锁定解除,以及错误处理等操作。
- 注册非
nil
函数,复制执行所需参数。 - 多个延迟调用按 FILO 次序执行。
- 运行时确保延迟调用总被执行。(
os.Exit
除外)
更详细的实现分析,请阅读《下卷: 8.6 延迟调用》。
func main() { f, err := os.Open("./main.go") if err != nil { log.Fatalln(err) } defer f.Close() b, err := io.ReadAll(f) if err != nil { log.Fatalln(err) } println(string(b)) }
func main() { defer println(1) // 此处是注册,而非执行。 defer println(2) // 注册需提供执行所需参数。 defer println(3) // 按 FILO 次序执行。 println("main") } // 退出前执行 defer。 /* main 3 2 1 */
func main() { var f func() // defer f() // ~~~~~~~~~ panic: invalid memory address or nil pointer dereference }
正常退出,或以 panic
中断,运行时都会确保延迟函数被执行。
func main() { defer println("defer!") panic("panic!") // 换成 os.Exit,会立即终止进程, } // 肯定不执行延迟调用。 /* defer! panic: panic! */
注意参数复制行为,必要时以指针或引用类型代替。
func main() { x := 100 defer println("defer:", x) x++ println("main:", x) } /* main: 101 defer: 100 */
返回值
延迟调用以闭包形式修改命名返回值,但须注意执行次序。
func test() (z int) { defer func() { z += 200 }() return 100 // return = (ret_val_z = 100, call defer, ret) } func main() { println(test()) // 300 } /* $ go build -gcflags "-S" --- test ----------------------- TEXT test(SB) z = 0 MOVQ $0, z+8(SP) +8 +---------+ | z | LEAQ test.func1(SB), AX 16 +---------+ MOVQ AX, +16(SP) | func1 |<--+ 24 +---------+ | LEAQ z+8(SP), AX | &z | | MOVQ AX, 24(SP) 32 +---------+ | | &func1 -|---+ LEAQ 16(SP), AX +---------+ MOVQ AX, 32(SP) z = 100 MOVQ $100, z+8(SP) call defer_func1 MOVQ 32(SP), DX MOVQ (DX), AX CALL AX ret z MOVQ z+8(SP), AX RET --- test.func1 ------------------- TEXT test.func1(SB) MOVQ 8(DX), AX ADDQ $200, (AX) RET */
为阅读方便,反汇编结果进行了调整。且所生成指令与编译器版本和优化有关,以实际输出为准。
从结果看,内联效果很好。从这点上说,
defer
不再是严重的性能关注点。
如果不是命名返回值,那么结果截然不同。
func test() int { z := 0 defer func() { z += 200 // 本地变量,与返回值无关。 }() z = 100 return z // return = (ret_val = 100, call defer, ret) } func main() { println(test()) // 100 }
误用
千万记住,延迟调用是在函数结束时才被执行。不合理的使用方式会浪费更多资源,甚至造成逻辑错误。
func main() { for i := 0; i < 10000; i++ { path := fmt.Sprintf("./log/%d.txt", i) f, err := os.Open(path) if err != nil { log.Println(err) continue } // 注册的 Close 直到函数结束时才执行,而非本次循环周期。 // 这无端延长了 10000 个 f 生命周期,平白消耗资源。 defer f.Close() //... do something ... } }
应该直接调用,或重构为函数,让循环和处理算法分离。
func main() { analyze = func(id int) { path := fmt.Sprintf("./log/%d.txt", id) f, err := os.Open(path) if err != nil { log.Println(err) return } // 现在不是问题了。 defer f.Close() //... do something ... } for i := 0; i < 10000; i++ { analyze(i) } }
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论