Decrypt Go: defer

makes the function simpler and more robust.

分享
Decrypt Go: defer
source:https://dev.to/amitiwary999/defer-in-golang-1lk0

In the previous article, we used defer to recover from panics. In the practical work of a gopher, defer acts like a loyal and reliable teammate, silently helping us with the clean-up work behind the scenes.
For example:

wg.Add(goroutines) 
 for i := 0; i < goroutines; i++ { 
     go func() { 
        defer wg.Done() 
        for j := 0; j < count/goroutines; j++ { 
           atomic.AddInt64(&sum, 1) 
        } 
     }() 
 } 
 wg.Wait()

defer is used to release locks or any other resources.

In Go, defer can only be used inside functions and methods. 
 The defer keyword must be followed by a function or method, which are referred to as deferred functions.

defer registers these functions into a stack data structure specific to the goroutine in which it is executed. The deferred functions are then scheduled to be executed in a last-in, first-out (LIFO) order before the function containing the defer statement exits.

Whether the function reaches the end of its body and returns, explicitly calls return in an error handling branch, or encounters a panic, the functions stored in the deferred function stack will be scheduled for execution. Thus, deferred functions provide a convenient way to perform clean-up tasks for a function in any scenario.

Several Use Cases for defer

  • Capturing panics: Since deferred functions are always executed in any scenario, we can handle exceptions within defer (although it is not recommended to use panic for general errors unless necessary).
  • Resource release: defer allows for graceful resource release, such as file descriptors or locks.
  • Delayed execution: defer can be used to record the execution time of a function, for example:
go func(s time.Time) { 
     fmt.Println(time.Now().Sub(s)) 
 }(time.Now())

Performance Overhead of defer

defer makes resource release (like file descriptors or locks) more elegant and less error-prone. However, in performance-sensitive programs, Gophers must be aware of and consider the performance burden introduced by defer.

In the following benchmark test, we can observe the performance difference between a version with defer and a version without defer:

hxzhouh  atomic  ➜ ( main  1)  ♥ 20:16  go test -bench=BenchmarkFooWithDefer  
 10000000 
 goos: darwin 
 goarch: arm64 
 pkg: github.com/hxzhouh/go-example/atomic 
 BenchmarkFooWithDefer-10        189423524                6.353 ns/op 
 PASS 
 ok      github.com/hxzhouh/go-example/atomic    3.631s 
 hxzhouh  atomic  ➜ ( main  1)  ♥ 21:05  go test -bench=BenchmarkFooWithoutDefer  
 BenchmarkFooWithoutDefer-10     273232389                4.397 ns/op 
 PASS 
 ok      github.com/hxzhouh/go-example/atomic    2.875s

In this test, the non-deferred version is approximately 7 times faster than the version with defer in Go 1.12. After optimization in versions 1.13 and 1.14, the performance defer has significantly improved. On my computer, the non-deferred version still has a performance advantage of about 50%.

Conclusion

In most cases, our programs are not highly sensitive to performance. I recommend using defer it whenever possible. However, it is important to understand how defer it works and a few things to avoid.