Go defer:makes the function simpler and more robust.

This article is first published in the medium MPP plan. If you are a medium user, please follow me in medium. Thank you very much.

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:

1
2
3
4
5
6
7
8
9
10
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.

Pasted image 20240229205228

Regardless of 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:
1
2
3
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:

1
2
3
4
5
6
7
8
9
10
11
12
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 of 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 whenever possible. However, it is important to understand how defer works, as well as a few things to avoid.