golang之defer
程序员文章站
2022-04-15 15:13:32
概述 对于资源释放,有很多不同的实现方式,不同语言也有不同的惯用方法。 C语言 :手动管理 Golang :defer Python :上下文管理器contexManager C++ : 作用域和析构函数 Rust :所有权和drop trait 如果了解上面几种语言的童鞋应该知道, C语言资源管理 ......
概述
对于资源释放,有很多不同的实现方式,不同语言也有不同的惯用方法。
- c语言 :手动管理
- golang :defer
- python :上下文管理器contexmanager
- c++ : 作用域和析构函数
- rust :所有权和drop trait
如果了解上面几种语言的童鞋应该知道,
c语言资源管理是比较麻烦的,一旦资源使用过程中出错,就可能造成资源泄漏。
golang通过defer,即使过程中panic,也可以释放资源。
python通过上下文管理器,主要是两个magic function`__enter__`, `__exit__`来保证资源的释放。
c++和rust相似,都是在某种语义下自动调用释放函数。但是rust有所有权检查,可以防止写代码犯傻 (比如c++不小心拷贝了一下。)
以上来看,c++和rust的必须要在编程时注意释放的时机,也就是需要程序员更多的思考。但是rust编译器会帮你一下,而c++并不会。
其次python和golang都使用了显式管理,一定不能忘了做,不过做了问题就不大了。
defer
不过这是一篇关于derfer的文章,写一下defer需要注意的重点(就是读effective go的一点笔记)。
基本使用
`defer语句将函数调用安排在当前函数结束前执行。也就是defer 语句中的函数调用是当前函数最后执行的东西`
- 一个经典的例子
1 // contents returns the file's contents as a string. 2 func contents(filename string) (string, error) { 3 f, err := os.open(filename) 4 if err != nil { 5 return "", err 6 } 7 defer f.close() // f.close will run when we're finished. 8 9 var result []byte 10 buf := make([]byte, 100) 11 for { 12 n, err := f.read(buf[0:]) 13 result = append(result, buf[0:n]...) // append is discussed later. 14 if err != nil { 15 if err == io.eof { 16 break 17 } 18 return "", err // f will be closed if we return here. 19 } 20 } 21 return string(result), nil // f will be closed if we return here. 22 }
用上面的话翻译代码中的`defer f.close()`就是“将f.close()放在contents函数的最后执行”
将`f.close()`放在defer后面有两个好处:保证资源释放、离`open`比较近不会忘了做。
这没什么好说的,golang必须显式释放资源。
细节
首先明确两个概念
- defer语句执行
将defer语句中函数调用安排在了当前函数结束前执行
- 函数调用执行
运行defer语句中的函数调用
参数求值
这里的参数求值指的是defer语句中函数调用的参数。
参数在defer语句执行求值,而不是在函数调用执行时求值。
- 又一个例子
func trace(s string) string { fmt.println("entering:", s) return s } func un(s string) { fmt.println("leaving:", s) } func a() { defer un(trace("a")) fmt.println("in a") } func b() { defer un(trace("b")) fmt.println("in b") a() } func main() { b() }
首先看函数b,因为参数是在defer语句执行时求值的,所以`trace("b")`要先被求值[先打印"b",再返回"b"],然后再往下执行,在函数b结束之前会调用`un("b")`.
同理如函数a。现在猜一下执行结果。
entering: b in b entering: a in a leaving: a leaving: b
执行顺序
当有多个defer语句的时候,到底是谁的函数调用先执行呢?
1 for i := 0; i < 5; i++ { 2 defer fmt.printf("%d ", i) 3 }
defer的函数调用按着后进先出(lifo)的方式执行。大概猜一猜,每次defer都会将函数调用压入栈中,最后依次出栈执行。
最后
golang中defer也就主要用在资源管理上了。明确以上几点问题,应该问题不大了(吹牛ing)。