9.7Go之函数之defer(延迟执行语句)

9.7Go之函数之defer(延迟执行语句)

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标记的语句相当于一个入栈模型,先入后出、后入先出

  • defer是在函数返回值返回前调用。特别注意。

posted @ 2021-09-07 19:39  俊king  阅读(126)  评论(0编辑  收藏  举报