设为首页 加入收藏

TOP

延宕执行,妙用无穷,Go lang1.18入门精炼教程,由白丁入鸿儒,Golang中defer关键字延迟调用机制使用EP17(一)
2023-07-23 13:27:04 】 浏览:50
Tags:lang1.18 白丁入 Golang defer 关键字 EP17

先行定义,延后执行。不得不佩服Go lang设计者天才的设计,事实上,defer关键字就相当于Python中的try{ ...}except{ ...}finally{...}结构设计中的finally语法块,函数结束时强制执行的代码逻辑,但是defer在语法结构上更加优雅,在函数退出前统一执行,可以随时增加defer语句,多用于系统资源的释放以及相关善后工作。当然了,这种流程结构是必须的,形式上可以不同,但底层原理是类似的,Golang 选择了更简约的defer,避免多级嵌套的try except finally 结构。

使用场景

操作系统资源在业务上避免不了的,比方说单例对象的使用权、文件读写、数据库读写、锁的获取和释放等等,这些资源需要在使用完之后释放掉或者销毁,如果忘记释放、资源会常驻内存,长此以往就会造成内存泄漏的问题。但是人非圣贤,孰能无过?因此研发者在撰写业务的时候有几率忘记关闭这些资源。

Golang中defer关键字的优势在于,在打开资源语句的下一行,就可以直接用defer语句来注册函数结束后执行关闭资源的操作。说白了就是给程序逻辑“上闹钟”,定义好逻辑结束时需要关闭什么资源,如此,就降低了忘记关闭资源的概率:

package main  
  
import (  
	"fmt"  
	"github.com/jinzhu/gorm"  
	_ "github.com/jinzhu/gorm/dialects/mysql"  
)  
  
func main() {  
	db, err := gorm.Open("mysql", "root:root@(localhost)/mytest?charset=utf8mb4&parseTime=True&loc=Local")  
  
	if err != nil {  
                fmt.Println(err)  
       		fmt.Println("连接数据库出错")  
		return  
	}  
  
	defer db.Close()  
        fmt.Println("链接Mysql成功")  
  
}

这里通过gorm获取数据库指针变量后,在业务开始之前就使用defer定义好数据库链接的关闭,在main函数执行完毕之前,执行db.Close()方法,所以打印语句是在defer之前执行的。

所以需要注意的是,defer最好在业务前面定义,如果在业务后面定义:

fmt.Println("链接Mysql成功")  
defer db.Close()

这样写就是画蛇添足了,因为本来就是结束前执行,这里再加个defer关键字的意义就不大了,反而会在编译的时候增加程序的判断逻辑,得不偿失。

defer执行顺序问题

Golang并不会限制defer关键字的数量,一个函数中允许多个“延迟任务”:

package main  
  
import "fmt"  
  
func main() {  
	defer func1()  
	defer func2()  
	defer func3()  
}  
  
func func1() {  
	fmt.Println("任务1")  
}  
  
func func2() {  
	fmt.Println("任务2")  
}  
  
func func3() {  
	fmt.Println("任务3")  
}

程序返回:

任务3  
任务2  
任务1

我们可以看到,多个defer的执行顺序其实是“反”着的,先定义的后执行,后定义的先执行,为什么?因为defer的执行逻辑其实是一种“压栈”行为:

package main  
  
import (  
	"fmt"  
	"sync"  
)  
  
// Item the type of the stack  
type Item interface{}  
  
// ItemStack the stack of Items  
type ItemStack struct {  
	items []Item  
	lock  sync.RWMutex  
}  
  
// New creates a new ItemStack  
func NewStack() *ItemStack {  
	s := &ItemStack{}  
	s.items = []Item{}  
	return s  
}  
  
// Pirnt prints all the elements  
func (s *ItemStack) Print() {  
	fmt.Println(s.items)  
}  
  
// Push adds an Item to the top of the stack  
func (s *ItemStack) Push(t Item) {  
	s.lock.Lock()  
	s.lock.Unlock()  
	s.items = append(s.items, t)  
}  
  
// Pop removes an Item from the top of the stack  
func (s *ItemStack) Pop() Item {  
	s.lock.Lock()  
	defer s.lock.Unlock()  
	if len(s.items) == 0 {  
		return nil  
	}  
	item := s.items[len(s.items)-1]  
	s.items = s.items[0 : len(s.items)-1]  
	return item  
}

这里我们使用切片和结构体实现了栈的数据结构,当元素入栈的时候,会进入栈底,后进的会把先进的压住,出栈则是后进的先出:

func main() {  
  
	var stack *ItemStack  
	stack = NewStack()  
	stack.Push("任务1")  
	stack.Push("任务2")  
	stack.Push("任务3")  
	fmt.Println(stack.Pop())  
	fmt.Println(stack.Pop())  
	fmt.Println(stack.Pop())  
  
}

程序返回:

任务3  
任务2  
任务1

所以,在defer执行顺序中,业务上需要先执行的一定要后定义,而业务上后执行的一定要先定义。

除此以外,就是与其他执行关键字的执行顺序问题,比方说return关键字:

package main  
  
import "fmt"  
  
func main() {  
	test()  
}  
  
func test() string {  
	defer fmt.Println("延时任务执行")  
	return testRet()  
}  
  
func testRet() string {  
	fmt.Println("返回值函数执行")  
	return ""  
}

程序返回:

返回值函数执行  
延时任务执行

一般情况下,我们会认为return就是结束逻辑,所以return逻辑应该会最后执行,但实际上defer会在retrun后面执行,所以defer中的逻辑如果依赖return中的执行结果,那么就绝对不能使用defer关键字。

业务与特性结合

我们

首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇我的设计模式之旅、04 解释器模式. 下一篇我的设计模式之旅、06 代理模式

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目