9.7Go之函数之defer(延迟执行语句)
defer的特点
- 
- 
在一个函数中先被 defer 的语句最后被执行,最后被 defer 的语句,最先被执行 
作用场景:
- 
用于释放某些已分配的资源。如互斥锁、关闭文件等 
defer的用法类似于java里面的finally语句块--->try...catch...finally
多个defer语句特点
- 
类似于栈,先入后出、后入先出 
示例:
package main
import "fmt"
func main() {
    fmt.Println("Begin")
    //使用defer语句类似于栈指针
    defer fmt.Println(1)
    defer fmt.Println(2)
    defer fmt.Println(3) //此时这个函数是最后入栈的,相当于位于栈顶。先出
    fmt.Println("End")
}
注意:
- 
延迟调用是在 defer 所在函数结束时进行,函数结束可以是正常返回时,也可以是发生宕机时 
使用延迟语句在函数退出时释放资源
处理开、关文件,接收、回复请求,加锁、解锁等操作容易忽略资源的释放
defer 语句是在函数退出时执行的语句,使用 defer 能非常方便地处理资源释放问题
使用延迟并发解锁
使用延迟释放文件句柄
使用延迟并发解锁
示例:
并发使用 map,使用 sync.Mutex 进行加锁,防止竞态问题
package main
import (
    "fmt"
    "sync"
)
var (
    //声明一个map
    valueKey = make(map[string]int)
    valueKeyGuard sync.Mutex
    /*
    map 默认不是并发安全的,准备一个 sync.Mutex 互斥量保护 map 的访问
    */
)
/*
声明一个函数,读取map里面的键和值,返回值,并且对竞争的资源加锁
 */
func readValue(key string) int {
    //加上资源锁
    valueKeyGuard.Lock()
    /*
    使用互斥量加锁
    */
    v := valueKey[key]
    //资源解锁
    valueKeyGuard.Unlock()
    /*
    使用互斥量解锁
    */
    return v
}
func main() {
    valueKey["name"] = 1
    //调用readValue方法
    fmt.Println(readValue("name"))
}
使用defer简化readValue函数
/*
使用defer简化上面的方法
 */
func readValue2(key string) int {
    //资源加锁
    valueKeyGuard.Lock()
    /*
    使用互斥量加锁
    */
    //使用defer让释放所最后执行
    defer valueKeyGuard.Unlock()
    /*
    该语句不会马上执行,而是等 readValue() 函数返回时才会被执行
    */
    //返回value
    return valueKey[key]
    /*
    从 map 查询值并返回的过程中,与不使用互斥量的写法一样
    */
}
使用延迟释放文件句柄
操作一个文件的流程:
- 
打开文件资源 
- 
获取、操作文件资源 
- 
关闭文件资源 
每一步系统操作都需要进行错误处理,而每一步处理都会造成一次可能的退出。因此要关注函数退出处正确释放资源
查询文件示例:
package main
import (
    "fmt"
    "os"
)
func fileSize(fileName string) int64 {
    //根据文件名打开文件, 返回文件句柄和错误
    f, err := os.Open(fileName)
    /*
    使用 os 包提供的函数 Open(),根据给定的文件名打开一个文件,并返回操作文件用的句柄和操作错误
    */
    if err != nil {
        return 0
    }
    //获取文件状态
    info, err := f.Stat()
    if err != nil {
        //关闭资源
        f.Close()
        return 0
    }
    /*
    处理错误,此时文件正常打开,要释放资源要调用f的close方法关闭文件
    */
    //取文件大小
    size := info.Size()
    //关闭资源
    f.Close()
    //返回名称和大小
    return size
}
func main() {
    fmt.Println(fileSize("APITest.jar"))
}
使用defer改良上面的函数:
/*
使用defer简化上面的函数。这里要注意:
1、defer是放在函数中最后执行的语句,相当于位于栈底
2、defer的声明式显式的
 */
func fileSize2(fileName string) int64 {
    //打开资源
    f, err:=os.Open(fileName)
    
    if err != nil {
        //返回0
        return 0
    }
    
    //正常打开资源接下来就是获取资源
    //声明一个遇到异常关闭函数的释放资源的方法
    defer f.Close()
    /*
    使用 defer,将 f.Close() 延迟调用
    不能将这一句代码放在上一个if判断前面,一旦文件打开错误,f 将为空,在延迟语句触发时,将触发宕机错误。
    defer 后的语句(f.Close())将会在函数返回前被调用,自动释放资源
    */
    
    //获取文件状态
    info, err:=f.Stat()
    if err != nil {
        //此时会结束函数,结束前会运行defer标记的内容,所以这里无需声明f.close()
        return 0
    }
    
    //获取文件大小
    size := info.Size()
    
    //返回size
    return size
}
章节小结:
- 
主要学习了 defer的特性,用于延迟调用语句。
- 
多个由 defer标记的语句相当于一个入栈模型,先入后出、后入先出
- 
    It's a lonely road!!!
 
                    
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号