更多关于延迟函数调用的知识点
在Go中,自定义函数的调用的返回结果都可以被舍弃。但是,大多数内置函数(除了和recover
)的调用的返回结果都不可以舍弃(至少对于标准编译器1.13来说是如此)。另一方面,我们已经了解到延迟函数调用的所有返回结果必须都舍弃掉。所以,很多内置函数是不能被延迟调用的。
幸运的是,在实践中,延迟调用内置函数的需求很少见。根据我的经验,只有append
函数有时可能会需要被延迟调用。对于这种情形,我们可以延迟调用一个调用了append
函数的匿名函数来满足这个需求。
一个被延迟调用的函数值是在其调用被推入延迟调用堆栈之前被估值的。例如,下面这个例子将输出false
。
package main
import "fmt"
func main() {
var f = func () {
fmt.Println(false)
}
defer f()
f = func () {
fmt.Println(true)
}
}
一个延迟调用的实参也是在此调用被推入延迟调用堆栈之前估值的。
一个例子:
import "os"
func withoutDefers(filepath string, head, body []byte) error {
f, err := os.Open(filepath)
if err != nil {
return err
}
_, err = f.Seek(16, 0)
if err != nil {
f.Close()
return err
if err != nil {
f.Close()
return err
}
_, err = f.Write(body)
if err != nil {
f.Close()
return err
}
err = f.Sync()
f.Close()
return err
}
func withDefers(filepath string, head, body []byte) error {
f, err := os.Open(filepath)
if err != nil {
return err
}
defer f.Close()
_, err = f.Seek(16, 0)
if err != nil {
return err
}
if err != nil {
return err
_, err = f.Write(body)
if err != nil {
return err
}
return f.Sync()
}
上面哪个函数看上去更简洁?显然,第二个使用了延迟调用的函数,虽然只是简洁了些许。另外第二个函数将导致更少的bug,因为第一个函数中含有太多的f.Close()
调用,从而有较高的几率漏掉其中一个。
下面是另外一个延迟调用使得代码更鲁棒的例子。如果doSomething
函数产生恐慌一个恐慌,则函数f2
在退出时将导致互斥锁未解锁。所以函数f1
更鲁棒。
一个较大的延迟调用堆栈可能会消耗很多内存,而且延迟调用堆栈中尚未执行的延迟调用可能会导致某些资源未被及时释放。比如,如果下面的例子中的函数需要处理大量的文件,则在此函数推出之前,将有大量的文件句柄得不到释放。
func writeManyFiles(files []File) error {
for _, file := range files {
f, err := os.Open(file.path)
if err != nil {
return err
}
defer f.Close()
_, err = f.WriteString(file.content)
if err != nil {
return err
}
err = f.Sync()
if err != nil {
return err
}
}
}
对于这种情形,我们应该使用一个匿名函数将需要及时执行延迟的调用包裹起来。比如,上面的函数可以改进为如下:
Go语言101项目目前同时托管在Github和上。欢迎各位在这两个项目中通过提交bug和PR的方式来改进完善Go语言101中的各篇文章。