社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
在go语言中,调用自定义函数的结果值可以全部不存在(丢弃)。但是,对于具有非空白返回结果列表的内置函数,他们的调用的结果不可以抛弃,copy和recover例外。 换句话说,延迟执行函数的结果必须被抛弃,所以许多内置函数无法被延迟。
幸运的是,在实践中,很有偶需要延迟执行内置函数的地方。据我所知,只有append函数可能需要被延迟执行。这种情况下,我们可以把append包装到一个延迟执行函数里。
package main
import "fmt"
func main() {
s := []string{"a", "b", "c", "d"}
defer fmt.Println(s) // [a x y d]
// defer append(s[:1], "x", "y") // error
defer func() {
_ = append(s[:1], "x", "y")
}()
}
延迟函数调用中的被调用函数可以是零函数值。对于这种情况,恐慌将在准备延迟调用被推入当前goroutine的延迟调用栈之前发生。
package main
import "fmt"
func main() {
defer fmt.Println("reachable")
var f func() // f is nil by default
defer f() // panic here
// The following lines are dead code.
fmt.Println("not reachable")
f = func() {}
}
在将延迟调用推入当前goroutine的延迟调用堆栈之前,还会计算延迟函数调用的参数
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
}
_, err = f.Write(head)
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
}
_, err = f.Write(head)
if err != nil {
return err
}
_, err = f.Write(body)
if err != nil {
return err
}
return f.Sync()
}
使用延迟函数调用并不总是好的。到目前为止(Go 1.12),对于官方Go编译器,延迟函数调用将在运行时导致一些性能损失.
例如,在以下示例中,CounterB和IncreaseB方法比CounterA和IncreaseA方法更有效。
import "sync"
type T struct {
mu sync.Mutex
n int64
}
func (t *T) CounterA() int64 {
t.mu.Lock()
defer t.mu.Unlock()
return t.n
}
func (t *T) CounterB() (count int64) {
t.mu.Lock()
count = t.n
t.mu.Unlock()
return
}
func (t *T) IncreaseA() {
t.mu.Lock()
defer t.mu.Unlock()
t.n++
}
func (t *T) IncreaseB() {
t.mu.Lock()
t.n++ // this line will not panic for sure
t.mu.Unlock()
}
在B版本的函数中,我们应该保证Lock和Unlock调用之间的代码永远不会出现恐慌。通常,建议在实践中使用A版本功能。当我们真正关心所涉及功能的性能时,我们应该只采用B版本。
非常大的延迟调用堆栈也可能消耗大量内存,并且未执行的延迟调用可能会阻止某些资源及时释放。例如,如果在调用以下函数时需要处理许多文件,则在函数退出之前将不会释放大量文件处理程序。
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
}
}
return nil
}
对于这种情况,我们可以使用匿名函数来包含延迟调用,以便延迟函数调用将更早执行。例如,上述功能可以重写和改进
func writeManyFiles(files []File) error {
for _, file := range files {
if err := func() error {
f, err := os.Open(file.path)
if err != nil {
return err
}
defer f.Close()
_, err = f.WriteString(file.content)
if err != nil {
return err
}
return f.Sync()
}(); err != nil {
return err
}
}
return nil
}
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!