4.5. 延迟执行语句-defer

Go语言的defer语句会将其后面跟随的语句进行延迟处理。 在defer归属的函数即将返回时,将延迟处理的语句按defer的逆序进行执行,也就是说,先被defer的语句 最后被执行,最后被defer的语句,最先被执行。

defer后面必须是函数或方法的调用,不能是语句。

defer语句必须先注册后才能执行,如果defer位于return之后,则defer因为没有注册,不会执行。

package main

func main() {
    defer func() {
        println("defer ")
    }()

    println("func body")
    a := 0
    println(a)
    return

    defer func() {
        println("second")
    }()
}

主动调用os.Exit(int)之后defer将不再被执行(即使defer已经提前被注册)

package main

import "os"

func main() {
    defer func() {
        println("defer ")
    }()

    println("func body")
    os.Exit(1)

}

4.5.1. 1.多个延迟执行语句的处理顺序

延迟执行语句示例1

package main

import "fmt"

func main() {
    fmt.Println("defer begin")
    // 将defer放入延迟调用栈
    defer fmt.Println(1)
    defer fmt.Println(2)
    // 最后一个放入,位于栈顶,最先调用
    defer fmt.Println(3)
    fmt.Println("defer end")
}

/*defer begin
defer end
3
2
1*/

代码的执行顺序与最终的执行顺序是反向的。 延迟调用是在defer所在函数结束时进行,函数结束可以是正常返回时,也可以是发生宕机时。

延迟执行语句示例2

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
        //t2.Close()
        defer t2.Close()
    }

}

多个 defer 注册,按 FILO 次序执行 ( 先进后出 )。哪怕函数或某个延迟调用发生错误,这些调用依旧会被执行。

package main

func test(x int) {
    defer println("a")
    defer println("b")

    defer func() {
        println(100/x)
    }()
    defer println("c")

}

func main() {
    test(0)
}

/*c
b
a
panic: runtime error: integer divide by zero*/

*滥用 defer 可能会导致性能问题,尤其是在一个 “大循环” 里。

package main

import (
    "fmt"
    "sync"
    "time"
)

var lock sync.Mutex

func test(x int) {
    lock.Lock()
    fmt.Printf("%d ",x)
    lock.Unlock()
}
func testdefer(x int) {
    lock.Lock()
    fmt.Printf("%d ",x)
    defer lock.Unlock()
}
func main() {
    func() {
        t1 := time.Now()
        for i := 0; i < 100; i++ {
            test(i)
        }
        elapsed := time.Since(t1)
        fmt.Println("test elapsed: ", elapsed)
    }()
    func() {
        t1 := time.Now()
        for i := 0; i < 100; i++ {
            testdefer(i)
        }
        elapsed := time.Since(t1)
        fmt.Println("testdefer elapsed: ", elapsed)
    }()
}

4.5.2. 2.使用延迟执行语句,在函数结束时自动释放资源

比如打开和关闭文件,接收请求和回复请求,加锁和解锁等,在这些操作中最容易忽略的是在每个函数退出时处正确 的释放和关闭资源。 defer正好是函数退出时执行的语句,所以defer能非常好的处理资源释放问题。

2.1 使用延迟并发解锁

解锁一个互斥所。

var mu sync.Mutex
var m = make(map[string] int)

func lookup(key string) int {
    mu.Lock()
    defer mu.Unlock()
    return m[key]
}

下面的例子会在函数中并发使用map,为防止竞态问题,使用sync.Mutex进行加锁。

package main

import (
    "fmt"
    "sync"
)

var (
    // 一个演示用的映射,实例化一个map,键是string,值是int
    valueByKey = make(map[string] int)

    // 保证使用映射时的并发安全的互斥锁
    valueByKeyGuard sync.Mutex
)

// 根据键读取值
func readValue(key string) int {
    // 对共享资源加锁,使用互斥量加锁
    valueByKeyGuard.Lock()

    //取值
    v :=valueByKey[key]

    // 对共享资源解锁,使用互斥量解锁
    valueByKeyGuard.Unlock()

    // 返回值,返回获取map的值
    return v
}
func main() {
    fmt.Println(readValue("hujianli"))
}

使用defer语句对上面的代码进行简化

package main

import (
    "fmt"
    "sync"
)

var (
    // 一个演示用的映射,实例化一个map,键是string,值是int
    valueByKey1 = make(map[string]int)

    // 保证使用映射时的并发安全的互斥锁
    valueByKeyGuard1 sync.Mutex
)

// 根据键读取值
func readValue1(key string) int {
    // 对共享资源加锁,使用互斥量加锁
    valueByKeyGuard1.Lock()

    // defer后面的语句不会马上调用,而是延迟到函数结束时调用
    defer valueByKeyGuard1.Unlock()
    // 返回值,返回获取map的值
    return valueByKey1[key]
}
func main() {
    fmt.Println(readValue1("hujianli"))
}

上面代码中使用defer添加解锁,该语句不会马上执行,而是等待readValue1返回时才会被执行。

2.2 使用延迟释放文件句柄

关闭一个打开的文件

func ReadFile(name string) ([]byte, error) {
    f, err := Open(name)
    if err != nil {
        return nil, err
    }
    defer f.Close()
    reutrn ReadAll(f)
}
package main

import (
    "fmt"
    "os"
)

// 根据文件名查询其大小,返回文件名和文件大小
func fileSize(filename string) (string, int64) {
    // 根据文件名打开文件,返回文件句柄和错误
    f, err := os.Open(filename)
    // 如果打开发生错误,返回文件大小为0
    if err != nil {
        return filename, 0
    }
    // 获取文件状态信息
    info, err := f.Stat()
    // 如果获取信息时发生错误,关闭文件并返回文件大小为0
    if err != nil {
        f.Close()
        return filename, 0
    }
    // 取文件名称和大小
    name := info.Name()
    size := info.Size()
    // 返回文件名称和大小
    return name, size
}
func main() {
    f_name, f_size := fileSize("deferTest03.go")
    fmt.Printf("name: %s  size: %dK", f_name, f_size)   //name: deferTest03.go  size: 606K
}

使用defer对代码进行优化

package main

import (
    "fmt"
    "os"
)

// 根据文件名查询其大小,返回文件名和文件大小
func fileSize(filename string) (string, int64) {
    // 根据文件名打开文件,返回文件句柄和错误
    f, error1 := os.Open(filename)
    // 如果打开发生错误,返回文件大小为0
    if error1 != nil {
        return filename, 0
    }
    // 延迟调用Close,此时Close不会调用
    defer f.Close()
    // 获取文件状态信息
    info, error2 := f.Stat()
    // 如果获取信息时发生错误,关闭文件并返回文件大小为0
    if error2 != nil {
        return filename, 0
    }
    // 取文件名称和大小
    name := info.Name()
    size := info.Size()
    // 返回文件名称和大小
    return name, size
}
func main() {
    f_name, f_size := fileSize("deferTest03.go")
    fmt.Printf("name: %s  size: %dK", f_name, f_size)   //name: deferTest03.go  size: 606K
}

文件句柄操作示例

package main

import (
    "bufio"
    "fmt"
    "io"
    "os"
)

func do() (err error) {
    file, error := os.Open("D:\\go_studay/go_path/src/github.com/function001/defer06/test.log")
    if error != nil {
        fmt.Println(error)
        return
    }
    //  看这里,这里是重点,如果打开正常,有数据,就正常关闭句柄
    if file != nil{
        defer func() {
            if f:=file.Close();f !=nil{
                err = f
            }
        }()
    }
    reader := bufio.NewReader(file)
    var line []byte
    for {
        data, prefix, err := reader.ReadLine()
        if err == io.EOF {
            break
        }

        line = append(line, data...)
        if !prefix {
            fmt.Printf("data:%s\n", string(line))
            line = line[:]
        }

    }
    return nil

}

func main() {
    do()
}

2.3 使用延迟释放网络连接句柄

package main

import (
    "fmt"
    "net/http"
)

func do() error {
    res,err :=http.Get("http://www.baidu.com")
    // 当且仅当 http.Get 成功执行时才使用 defer
    if res != nil {
        defer res.Body.Close()
    }
    if err != nil {
        return err
    }
    fmt.Println(res)
    return nil
}

func main() {
    do()
}