Multiple defers vs deferred anonymous function
In this example, the anonymous function is easier to read, especially once you add in error handling.
f, err := ioutil.TempFile("", "prefix")
if err != nil {
log.Println("creating temp file:", err)
return
}
defer func() {
err := f.Close()
if err != nil {
log.Println("close:", err)
}
err = os.Remove(f.Name())
if err != nil {
log.Println("remove:", err)
}
}()
If you have multiple resources, then multiple defer
s is generally appropriate.
As Ross Light answer states:
If you have multiple resources, then multiple defers is generally appropriate.
April 2019: But in that case, consider Go 1.13 (Q4 2019), as it does integrate a fix for go issue 14939: "runtime: defer is slow" and go issue 6980: "cmd/compile: allocate some defers in stack frames"
See Go CL 171758: "cmd/compile,runtime: allocate defer records on the stack"
When a defer is executed at most once in a function body, we can allocate the defer record for it on the stack instead of on the heap.
This should make defers like this (which are very common) faster.
This optimization applies to 363 out of the 370 static defer sites in the cmd/go binary.
name old time/op new time/op delta Defer-4 52.2ns ± 5% 36.2ns ± 3% -30.70% (p=0.000 n=10+10)
Oct. 2019 (Go 1.13 is released a few weeks ago)
This is confirmed (Brad Fitzpatrick) with CL 190098:
Cost of defer statement [
go test -run NONE -bench BenchmarkDefer$ runtime
]
With normal (stack-allocated) defers only: 35.4 ns/op With open-coded defers: 5.6 ns/op Cost of function call alone (remove defer keyword): 4.4 ns/op
But Damien Grisky adds:
Defer gets cheaper, but panic/recover is more expensive.
Cost of defer: 34ns -> 6ns. Cost of panic/recover: 62ns -> 255ns
That is not a bad trade-off.
In other words, while using multiple defer can be idiomatic, that practice was held back by performance costs which are no longer a concern with Go 1.13+.
(as illustrated by Paschalis's blog post "What is a defer? And how many can you run?")
That makes practical use if defer (in places where a function call should be executed irrespective of the code flow) possible.
John Refior notes, however, that defer
is synchronous:
Actually defer is executed immediately before the function exits.
And it occurs synchronously, so the caller waits for defer to complete.
So even if you can now have multiple defer, make sure they are fast, or, as John notes:
Fortunately it’s easy to wrap a goroutine in a
defer
, giving us the flow control and timing we want, without delaying the caller:
func Handler(w http.ResponseWriter, r *http.Request) {
log.Println("Entered Handler")
defer func() {
go func() {
time.Sleep(5 * time.Second)
log.Println("Exiting goroutine")
}()
log.Println("Exiting defer")
}()
}
Often defers are used for locking a mutex, or closing a connection or file descriptor, and the work they do is fast, or we want it to complete before the caller moves on.
But when you’re doing slow work that the client shouldn’t need to wait for at the end of an HTTP handler, making the call asynchronous can substantially improve user experience.