Article directory
delay calling defer
defer features:
1. 关键字 defer 用于注册延迟调用。
2. 这些调用直到 return 前才被执。因此,可以用来做资源清理。
3. 多个defer语句,按先进后出的方式执行。
4. defer语句中的变量,在defer声明时就决定了。
Use of defer:
1. 关闭文件句柄
2. 锁资源释放
3. 数据库连接释放
- defer is used to register a deferred call (called before the function returns).
- The typical application scenario of defer is to release resources, such as closing file handles and releasing database connections.
- If there are multiple defers in the same function, the one registered later will be executed first.
- Defer can be followed by a func. If a panic occurs inside the func, the panic will be put on hold temporarily, and this will be executed after the other defers are executed.
- After defer is not followed by func, but directly followed by an execution statement, the relevant variables will be copied or calculated when registering defer.
func basic() {
fmt.Println("A")
defer fmt.Println(1)
//如果同一个函数里有多个defer,则后注册的先执行
defer fmt.Println(2)
fmt.Println("C")
}
// A C 2 1
func defer_exe_time() (i int) {
i = 9
defer func() {
//defer后可以跟一个func
fmt.Printf("first i=%d\n", i) //打印5,而非9。充分理解“defer在函数返回前执行”的含义,不是在“return语句前执行defer”
}()
defer func(i int) {
fmt.Printf("second i=%d\n", i) //打印9
}(i)
defer fmt.Printf("third i=%d\n", i) //defer后不是跟func,而直接跟一条执行语句,则相关变量在注册defer时被拷贝或计算
return 5
}
third i=9
second i=9
first i=5
func defer_panic() {
defer fmt.Println("111")
n := 0
defer func() {
fmt.Println(2 / n)
fmt.Println("222")
}()
fmt.Println("333")
}
333
111
panic
defer encounters a closure
package main
import "fmt"
func main() {
var whatever [5]struct{
}
for i := range whatever {
defer func() {
fmt.Println(i) }()
}
}
Output result:
4
4
4
4
4
In fact, go is very clear, let's take a look at what go spec says
Each time a “defer” statement executes, the function value and parameters to the call are evaluated as usualand saved anew but the actual function is not invoked.
That is to say, the function is executed normally. Since the variable i used by the closure has become 4 during execution, the output is all 4.
defer f.Close
Everyone uses this very frequently, but go language programming gives an example that you may accidentally make a mistake.
package main
import "fmt"
type Test struct {
name string
}
func (t *Test) Close() {
fmt.Println(t.name, " closed")
}
func main() {
ts := []Test{
{
"a"}, {
"b"}, {
"c"}}
for _, t := range ts {
defer t.Close()
}
}
Output result:
c closed
c closed
c closed
This output will not output cba as we expected, but output ccc
But according to the instructions in the previous go spec, it should output cba.
Then let's call it another way.
package main
import "fmt"
type Test struct {
name string
}
func (t *Test) Close() {
fmt.Println(t.name, " closed")
}
func Close(t Test) {
t.Close()
}
func main() {
ts := []Test{
{
"a"}, {
"b"}, {
"c"}}
for _, t := range ts {
defer Close(t)
}
}
Output result:
c closed
b closed
a closed
At this time, the output is cba
Of course, if you don’t want to write one more function, it’s also very simple. You can also output cba like the following
seemingly superfluous statement
package main
import "fmt"
type Test struct {
name string
}
func (t *Test) Close() {
fmt.Println(t.name, " closed")
}
func main() {
ts := []Test{
{
"a"}, {
"b"}, {
"c"}}
for _, t := range ts {
t2 := t
defer t2.Close()
}
}
Output result:
c closed
b closed
a closed
Through the above example, combined with
Each time a “defer” statement executes, the function value and parameters to the call are evaluated as usualand saved anew but the actual function is not invoked.
This sentence. The following conclusions can be drawn:
When the statement after defer is executed, the parameters of the function call will be saved, but not executed. That is, a copy. But it doesn't say how to deal with the this pointer here in the struct. From this example, it can be seen that the go language does not treat the explicitly written this pointer as a parameter.
Multiple defer registrations are executed in FILO order (first in, last out). Even if an error occurs in a function or a deferred call, these calls will still be executed.
package main
func test(x int) {
defer println("a")
defer println("b")
defer func() {
println(100 / x) // div0 异常未被捕获,逐步往外传递,最终终止进程。
}()
defer println("c")
}
func main() {
test(0)
}
output result:
c
b
a
panic: runtime error: integer divide by zero
*
Deferred call parameters are evaluated or copied at registration time, and can be read "lazily" using pointers or closures.
package main
func test() {
x, y := 10, 20
defer func(i int) {
println("defer:", i, y) // y 闭包引用
}(x) // x 被复制
x += 10
y += 100
println("x =", x, "y =", y)
}
func main() {
test()
}
output result:
x = 20 y = 120
defer: 10 120
*
Abuse of defer can cause performance problems, especially in a "big loop".
package main
import (
"fmt"
"sync"
"time"
)
var lock sync.Mutex
func test() {
lock.Lock()
lock.Unlock()
}
func testdefer() {
lock.Lock()
defer lock.Unlock()
}
func main() {
func() {
t1 := time.Now()
for i := 0; i < 10000; i++ {
test()
}
elapsed := time.Since(t1)
fmt.Println("test elapsed: ", elapsed)
}()
func() {
t1 := time.Now()
for i := 0; i < 10000; i++ {
testdefer()
}
elapsed := time.Since(t1)
fmt.Println("testdefer elapsed: ", elapsed)
}()
}
output result:
test elapsed: 223.162µs
testdefer elapsed: 781.304µs
defer trap
defer 与 closure
package main
import (
"errors"
"fmt"
)
func foo(a, b int) (i int, err error) {
defer fmt.Printf("first defer err %v\n", err)
defer func(err error) {
fmt.Printf("second defer err %v\n", err) }(err)
defer func() {
fmt.Printf("third defer err %v\n", err) }()
if b == 0 {
err = errors.New("divided by zero!")
return
}
i = a / b
return
}
func main() {
foo(2, 0)
}
Output result:
third defer err divided by zero!
second defer err <nil>
first defer err <nil>
Explanation: If defer is not followed by a closure, we will not get the latest value when it is executed last.
defer and return
package main
import "fmt"
func foo() (i int) {
i = 0
defer func() {
fmt.Println(i)
}()
return 2
}
func main() {
foo()
}
Output result:
2
Explanation: In a function with a named return value (here the named return value is i), the value of i has actually been reassigned to 2 when return 2 is executed. So the output of defer closure is 2 instead of 1.
defer nil function
package main
import (
"fmt"
)
func test() {
var run func() = nil
defer run()
fmt.Println("runs")
}
func main() {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
test()
}
Output result:
runs
runtime error: invalid memory address or nil pointer dereference
Explanation: The function named test runs to the end, then the defer function will be executed and a panic exception will be generated because the value is nil. However, it is worth noting that the statement of run() is fine, because it will not be called until the test function has finished running.
Using defer in the wrong place
Exception thrown when http.Get fails.
package main
import "net/http"
func do() error {
res, err := http.Get("http://www.google.com")
defer res.Body.Close()
if err != nil {
return err
}
// ..code...
return nil
}
func main() {
do()
}
Output result:
panic: runtime error: invalid memory address or nil pointer dereference
Because here we did not check whether our request was successfully executed, when it failed, we accessed the empty variable res in the Body, so an exception will be thrown
solution
Always use defer after a successful resource allocation, which means in this case: use defer if and only if http.Get executes successfully
package main
import "net/http"
func do() error {
res, err := http.Get("http://xxxxxxxxxx")
if res != nil {
defer res.Body.Close()
}
if err != nil {
return err
}
// ..code...
return nil
}
func main() {
do()
}
In the above code, when there is an error, err will be returned, otherwise when the entire function returns, res.Body will be closed.
Explanation: Here, you also need to check whether the value of res is nil, which is a warning in http.Get. Normally, when an error occurs, the returned content should be empty and the error will be returned, but when you get a redirection error, the value of res will not be nil, but it will return the error. The above code guarantees that the Body will be closed no matter what, if you do not intend to use the data in it, then you also need to discard the received data.
do not check for errors
Here, f.Close() may return an error, but this error will be ignored by us
package main
import "os"
func do() error {
f, err := os.Open("book.txt")
if err != nil {
return err
}
if f != nil {
defer f.Close()
}
// ..code...
return nil
}
func main() {
do()
}
improve
package main
import "os"
func do() error {
f, err := os.Open("book.txt")
if err != nil {
return err
}
if f != nil {
defer func() {
if err := f.Close(); err != nil {
// log etc
}
}()
}
// ..code...
return nil
}
func main() {
do()
}
improve it
Errors within defer are returned via named return variables.
package main
import "os"
func do() (err error) {
f, err := os.Open("book.txt")
if err != nil {
return err
}
if f != nil {
defer func() {
if ferr := f.Close(); ferr != nil {
err = ferr
}
}()
}
// ..code...
return nil
}
func main() {
do()
}
release the same resource
If you try to release different resources with the same variable, this operation may not work properly.
package main
import (
"fmt"
"os"
)
func do() error {
f, err := os.Open("book.txt")
if err != nil {
return err
}
if f != nil {
defer func() {
if err := f.Close(); err != nil {
fmt.Printf("defer close book.txt err %v\n", err)
}
}()
}
// ..code...
f, err = os.Open("another-book.txt")
if err != nil {
return err
}
if f != nil {
defer func() {
if err := f.Close(); err != nil {
fmt.Printf("defer close another-book.txt err %v\n", err)
}
}()
}
return nil
}
func main() {
do()
}
输出结果:
defer close book.txt err close ./another-book.txt: file already closed
When the deferred function executes, only the last variable will be used, so the f variable will become the last resource (another-book.txt). And both defers will close this resource as the last resource
solution:
package main
import (
"fmt"
"io"
"os"
)
func do() error {
f, err := os.Open("book.txt")
if err != nil {
return err
}
if f != nil {
defer func(f io.Closer) {
if err := f.Close(); err != nil {
fmt.Printf("defer close book.txt err %v\n", err)
}
}(f)
}
// ..code...
f, err = os.Open("another-book.txt")
if err != nil {
return err
}
if f != nil {
defer func(f io.Closer) {
if err := f.Close(); err != nil {
fmt.Printf("defer close another-book.txt err %v\n", err)
}
}(f)
}
return nil
}
func main() {
do()
}