当前位置: 首页 > article >正文

Go基础编程 - 15 - 延迟调用(defer)

延迟调用 defer

    • 1. 特性
    • 2. 常用用途
    • 3. defer 执行顺序:`同函数内`先进后出
    • 4. defer 闭包
    • 5. defer 陷阱


上一篇:泛型


1. 特性

1. 关键字 defer 用于注册延迟调用。
2. defer 调用直到 return 前才被执行。
3. 同函数内多个 defer 语句,按先进后出的方式执行。
4. defer 语句中的变量,在 defer 声明时就决定了。

2. 常用用途

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

1. 关闭文件句柄。
2. 锁资源释放。
3. 数据库连接释放。

3. defer 执行顺序:同函数内先进后出

package main

import "fmt"

func defer1(i int) {
	defer fmt.Printf("defer1 %d - 1\n", i)
	defer fmt.Printf("defer1 %d - 2\n", i)
}

func main() {
	// 函数内 defer,在函数执行结束前执行。
	// 因此嵌套函数 defer1() 内的 defer 在自身函数内按先进后出的顺序执行。
	// 且 defer1() 内的 defer 在 main 函数内的执行顺序仅取决于 defer1() 函数的执行顺序。
	defer defer1(100)
	defer1(200)		 

	defer fmt.Println("main 1")
	
	defer1(300)			
	defer defer1(400)	
}

以上代码执行顺序及输出结果如下图:

  1. 函数内 defer,在函数执行结束前执行。
  2. main 函数内,关键字 defer 在 main 函数内按先进后出顺序在 main 函数执行结束前执行,其它代码按顺序执行。
  3. defer1() 函数内关键字 defer 在 defer1 函数内按先进后出顺序执行。
  4. defer1() 函数内的输出在自身函数执行结束前全部输出。
    在这里插入图片描述

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

func main() {
	defer println(1)
	defer println(2)

	i := 0
	defer func() {
		println(100 / i)	// 此处抛出 panic,但下面 defer 依然执行
	}()

	defer println(3)
}

输出结果

3
2
1
panic: runtime error: integer divide by zero

4. defer 闭包

defer 语句适用于单个函数或语句的延迟执行。当需要执行更复杂的逻辑或者多个操作时,可以使用 defer func () 闭包。

  • 每次执行 defer 语句时,函数值调用的参数都会进行求值并保存,但不执行实际的函数。 也就是复制了一份。

    func main() {
    	i := 1
    	defer fmt.Println("one - i =", i) // i 被复制保存
    	
    	// defer 使用了闭包, 此时仅定义了闭包函数,并未执行。
    	defer func() {
    		fmt.Println("two - i =", i)  // 闭包引用 i
    	}()
    
    	// 变量 i 作为函数参数传入时,会进行求值并保存。
    	defer func(n int) {
    		fmt.Println("three - i =", n)
    	}(i) // i 被复制保存
    
    	i = 100
    	
    	defer func(i int) {
    		fmt.Println("four - i =", i)
    	}(i) // i 被复制保存
    
    	fmt.Println("i =", i)
    }
    

    输出结果:

    i = 100
    four - i = 100
    three - i = 1
    two - i = 100
    one - i = 1
    
  • defer 中可以使用指针或闭包“延迟”读取。

    func main() {
    	x, y, z := 10, 10, 10
    	
    	// 延迟读取 y,z 的值
    	defer func(i int, j *int) {
    		fmt.Printf("defer: x = %d, y = %d, z = %d \n", i, *j, z) // y 指针传递,z 闭包引用
    	}(x, &y) // x 被复制
    
    	x += 100
    	y += 100
    	z += 100
    	fmt.Printf("main: x = %d, y = %d, z = %d\n", x, y, z)
    }
    

    输出结果:

    main: x = 110, y = 110, z = 110
    defer: x = 10, y = 110, z = 110
    

5. defer 陷阱

  • defer 与闭包、return

    package main
    
    import (
        "errors"
        "fmt"
        "os"
    )
    
    // 如果 defer 后不是一个闭包,最后执行的时候我们得到的并不是最新的值,而是声明 defer 时保存的值。
    // 有具名返回值函数中,实际值为return前最终计算结果。
    func closure(i, j int) (n int, err error) {
    	defer fmt.Printf("1 n = %d, defer: %v\n", err) // n, err 复制保存当前值
    
    	defer func(err error) {
    		fmt.Printf("2 n = %d, defer: %v\n", err)
    	}(err) // err 保存当前值传参;n 为全局变量
    
    	defer func() {
    		fmt.Printf("3 n = %d, defer: %v\n", err)
    	}() // 闭包引用
    
    	if j == 0 {
    		err = errors.New("divided by zero")
    		return
    	}
    
    	return i / j, nil
    }
    
    // 使用相同的变量释放不同的资源,那么这个操作可能无法正常执行。
    // 1. 示例中打开test.txt、test2.txt都赋值给变量 f。
    // 2. defer 声明使用闭包函数;在执行defer声明的代码时,f 已经被重新赋值为 test2.txt,导致 test.txt 关闭失败。
    //    报错:close failed:test.txt close test2.txt: file already closed
    // 3. 优化建议:避免使用相同变量;或 defer 声明使用值参传参
    func closureCover() {
    	f, err := os.Open("test.txt")
    	if err != nil {
    		fmt.Println("open failed: test.txt")
    	}
    	if f != nil {
    		defer func() {
    			if err := f.Close(); err != nil {
    				fmt.Println("close failed:test.txt", err)
    			}
    		}()
    		// 优化为值传递
    		/*
    		defer func(f *os.File) {
    			...
    		}(f)
    		*/
    	}
    	
    	f, err = os.Open("test2.txt")
    	if err != nil {
    		fmt.Println("open failed: test2.txt")
    	}
    	if f != nil {
    		defer func() {
    			if err := f.Close(); err != nil {
    				fmt.Println("close failed:test2.txt", err)
    			}
    		}() // 优化同上
    	}
    }
    
    
    func main() {
    	closure(2, 0)
    	// 输出:
     	// 3 n = 0, defer: divided by zero
    	// 2 n = 0, defer: <nil>
    	// 1 n = 0, defer: <nil>
    
    	// 有具名返回值函数中,实际值为return前最终计算结果。输出:
    	// 3 n = 5, defer: <nil>
    	// 2 n = 5, defer: <nil>
    	// 1 n = 0, defer: <nil>
    }
    
  • defer nil 函数

    func main() {
    	defer func() {
    		if err := recover(); err != nil {
    			fmt.Println("recover:", err)
    		}
    	}()
    
    	var run func() = nil
    	defer run()
    
    	fmt.Println("running")
    	// 输出:
    	// running
    	// recover: runtime error: invalid memory address or nil pointer dereference
    }
    
  • 在错误位置使用 defer

    package main
    
    import (
    	"fmt"
    	"net/http"
    )
    
    func main() {
    
    	res, err := http.Get("http://www.xxxxxxxxxxx")
    	defer res.Body.Close()
    
    	if err == nil {
    		return
    	}
    	fmt.Println("running")
    	// 输出
    	// panic: runtime error: invalid memory address or nil pointer dereference
    }
    

    当 http.Get 失败时 res 为 nil,我们在 defer 调用 res.Body 时并未判断是否执行成功,会抛出异常。优化如下:

    func main() {
       res, err := http.Get("http://www.xxxxxxxxxxx")
       if err == nil {	 // 判断 http.Get 执行成功时,才需要关闭响应连接
       	defer res.Body.Close()
       }
       fmt.Println("running")
       // 输出
       // running
    }
    

http://www.kler.cn/news/330594.html

相关文章:

  • Flume面试整理-Flume是什么?
  • 滚雪球学Oracle[5.1讲]:Oracle数据库管理与维护
  • 基于单片机的花色可调跑马灯设计
  • Library介绍(三)
  • 从0到1深入浅出构建Nest.Js项目
  • 如何伪装服务器地址?
  • 基于SSH的酒店管理系统的设计与实现 (含源码+sql+视频导入教程+文档+PPT)
  • 三大马车;预征税赋;贷款供楼
  • python中的find函数怎么用
  • 51单片机的串口
  • 算法打卡:第十一章 图论part10
  • MySQL的优化手段
  • YOLO11项目实战1:道路缺陷检测系统设计【Python源码+数据集+运行演示】
  • Spark 中所有用到了Job对象的组件模块和关系
  • windows10或11家庭版实现远程桌面连接控制
  • 【GO语言】卡尔曼滤波例程
  • MySQL 实验 2:数据库的创建与管理
  • 管理方法(12)-- 采购管理
  • Elasticsearch 实战应用:从入门到项目集成
  • [2024年]最新VMware Workstation虚拟机下载 带链接