返回顶部
首页 > 资讯 > 精选 >GO语言中defer实现原理是什么
  • 242
分享到

GO语言中defer实现原理是什么

2023-07-05 06:07:24 242人浏览 安东尼
摘要

这篇文章主要介绍“Go语言中defer实现原理是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“GO语言中defer实现原理是什么”文章能帮助大家解决问题。defer 是什么咱们一起来看看 def

这篇文章主要介绍“Go语言中defer实现原理是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“GO语言中defer实现原理是什么”文章能帮助大家解决问题。

    defer 是什么

    咱们一起来看看 defer 是个啥

    是 GO 中的一个关键字

    这个关键字,我们一般用在释放资源,在 return 前会调用他

    如果程序中有多个 defer ,defer 的调用顺序是按照类似的方式,后进先出 LIFO的 ,这里顺便写一下

    GO语言中defer实现原理是什么

    遵循后进先出原则

    后进入栈的,先出栈

    先进入栈的,后出栈

    队列

    GO语言中defer实现原理是什么

    遵循先进先出 , 我们就可以想象一个单向的管道,从左边进,右边出

    先进来,先出去

    后进来,后出去,不准插队

    defer 实现原理

    咱们先抛出一个结论,先心里有点底:

    代码中声明 defer的位置,编译的时候会插入一个函数叫做 deferproc ,在该defer所在的函数前插入一个返回的函数,不是return 哦,是deferreturn

    具体的 defer 的实现原理是咋样的,我们还是一样的,来看看 defer的底层数据结构是啥样的 ,

    src/runtime/runtime2.gotype _defer struct {结构

    // A _defer holds an entry on the list of deferred calls.// If you add a field here, add code to clear it in freedefer and deferProcStack// This struct must match the code in cmd/compile/internal/GC/reflect.go:deferstruct// and cmd/compile/internal/gc/ssa.go:(*state).call.// Some defers will be allocated on the stack and some on the heap.// All defers are logically part of the stack, so write barriers to// initialize them are not required. All defers must be manually scanned,// and for heap defers, marked.type _defer struct {   siz     int32 // includes both arguments and results   started bool   heap    bool   // openDefer indicates that this _defer is for a frame with open-coded   // defers. We have only one defer record for the entire frame (which may   // currently have 0, 1, or more defers active).   openDefer bool   sp        uintptr  // sp at time of defer   pc        uintptr  // pc at time of defer   fn        *funcval // can be nil for open-coded defers   _panic    *_panic  // panic that is running defer   link      *_defer   // If openDefer is true, the fields below record values about the stack   // frame and associated function that has the open-coded defer(s). sp   // above will be the sp for the frame, and pc will be address of the   // deferreturn call in the function.   fd   unsafe.Pointer // funcdata for the function associated with the frame   varp uintptr        // value of varp for the stack frame   // framepc is the current pc associated with the stack frame. Together,   // with sp above (which is the sp associated with the stack frame),   // framepc/sp can be used as pc/sp pair to continue a stack trace via   // gentraceback().   framepc uintptr}

    _defer 持有延迟调用列表中的一个条目 ,我们来看看上述数据结构的参数都是啥意思

    tag说明
    sizdefer函数的参数和结果的内存大小
    fn需要被延迟执行的函数
    _panicdefer 的 panic 结构体
    link同一个协程里面的defer 延迟函数,会通过该指针连接在一起
    heap是否分配在堆上面
    openDefer是否经过开放编码优化
    sp栈指针(一般会对应到汇编)
    pc程序计数器

    defer 关键字后面必须是跟函数,这一点咱们要记住哦

    通过上述参数的描述,我们可以知道,defer的数据结构和函数类似,也是有如下三个参数:

    • 栈指针 SP

    • 程序计数器 PC

    • 函数的地址

    可是我们是不是也发现了,成员里面还有一个link,同一个协程里面的defer 延迟函数,会通过该指针连接在一起

    这个link指针,是指向的一个defer链表的头,每次咱们声明一个defer的时候,就会将该defer的数据插入到这个单链表头部的位置,

    那么,执行defer的时候,我们是不是就能猜到defer 是咋取得了不?

    前面有说到defer是后进先出的,这里当然也是遵循这个道理,取defer进行执行的时候,是从单链表的头开始去取的。

    咱们来画个图形象一点

    在协程A中声明2defer,先声明 defer test1()

    GO语言中defer实现原理是什么

    再声明 defer test2()

    GO语言中defer实现原理是什么

    可以看出后声明的defer会插入到单链表的头,先声明的defer被排到后面去了

    咱们取的时候也是一直取头下来执行,直到单链表为空。

    咱一起来看看defer 的具体实现

    GO语言中defer实现原理是什么

    源码文件在 src/runtime/panic.go 中,查看 函数 deferproc

    // Create a new deferred function fn with siz bytes of arguments.// The compiler turns a defer statement into a call to this.//go:nosplitfunc deferproc(siz int32, fn *funcval) { // arguments of fn follow fn   gp := getg()   if gp.m.curg != gp {      // go code on the system stack can't defer      throw("defer on system stack")   }   // the arguments of fn are in a perilous state. The stack map   // for deferproc does not describe them. So we can't let garbage   // collection or stack copying trigger until we've copied them out   // to somewhere safe. The memmove below does that.   // Until the copy completes, we can only call nosplit routines.   sp := getcallersp()   argp := uintptr(unsafe.Pointer(&fn)) + unsafe.Sizeof(fn)   callerpc := getcallerpc()   d := newdefer(siz)   if d._panic != nil {      throw("deferproc: d.panic != nil after newdefer")   }   d.link = gp._defer   gp._defer = d   d.fn = fn   d.pc = callerpc   d.sp = sp   switch siz {   case 0:      // Do nothing.   case sys.PtrSize:      *(*uintptr)(deferArgs(d)) = *(*uintptr)(unsafe.Pointer(argp))   default:      memmove(deferArgs(d), unsafe.Pointer(argp), uintptr(siz))   }   // deferproc returns 0 nORMally.   // a deferred func that stops a panic   // makes the deferproc return 1.   // the code the compiler generates always   // checks the return value and jumps to the   // end of the function if deferproc returns != 0.   return0()   // No code can go here - the C return reGISter has   // been set and must not be clobbered.}

    deferproc 的作用是

    创建一个新的递延函数 fn,参数为 siz 字节,编译器将一个延迟语句转换为对this的调用

    getcallersp()

    得到deferproc之前的rsp寄存器的值,实现的方式所有平台都是一样的

    //go:noescapefunc getcallersp() uintptr // implemented as an intrinsic on all platforms

    callerpc := getcallerpc()

    此处得到 rsp之后,存储在 callerpc 中 , 此处是为了调用 deferproc 的下一条指令

    d := newdefer(siz)

    d := newdefer(siz) 新建一个defer 的结构,后续的代码是在给defer 这个结构的成员赋值

    咱看看 deferproc 的大体流程

    • 获取 deferproc之前的rsp寄存器的值

    • 使用newdefer 分配一个 _defer 结构体对象,并且将他放到当前的 _defer 链表的头

    • 初始化_defer 的相关成员参数

    • return0

    来我们看看 newdefer的源码

    源码文件在 src/runtime/panic.go 中,查看函数newdefer

    // Allocate a Defer, usually using per-P pool.// Each defer must be released with freedefer.  The defer is not// added to any defer chain yet.//// This must not grow the stack because there may be a frame without// stack map information when this is called.////go:nosplitfunc newdefer(siz int32) *_defer {var d *_defersc := deferclass(uintptr(siz))gp := getg()if sc < uintptr(len(p{}.deferpool)) {pp := gp.m.p.ptr()if len(pp.deferpool[sc]) == 0 && sched.deferpool[sc] != nil {// Take the slow path on the system stack so// we don't grow newdefer's stack.systemstack(func() {lock(&sched.deferlock)for len(pp.deferpool[sc]) < cap(pp.deferpool[sc])/2 && sched.deferpool[sc] != nil {d := sched.deferpool[sc]sched.deferpool[sc] = d.linkd.link = nilpp.deferpool[sc] = append(pp.deferpool[sc], d)}unlock(&sched.deferlock)})}if n := len(pp.deferpool[sc]); n > 0 {d = pp.deferpool[sc][n-1]pp.deferpool[sc][n-1] = nilpp.deferpool[sc] = pp.deferpool[sc][:n-1]}}if d == nil {// Allocate new defer+args.systemstack(func() {total := roundupsize(totaldefersize(uintptr(siz)))d = (*_defer)(mallocgc(total, deferType, true))})}d.siz = sizd.heap = truereturn d}

    newderfer 的作用:

    通常使用per-P池,分配一个Defer

    每个defer可以自由的释放。当前defer 也不会加入任何一个 defer链条中

    getg()

    获取当前协程的结构体指针

    // getg returns the pointer to the current g.// The compiler rewrites calls to this function into instructions// that fetch the g directly (from TLS or from the dedicated register).func getg() *g

    pp := gp.m.p.ptr()

    拿到当前工作线程里面的 P

    然后拿到 从全局的对象池子中拿一部分对象给到P的池子里面

    for len(pp.deferpool[sc]) < cap(pp.deferpool[sc])/2 && sched.deferpool[sc] != nil {d := sched.deferpool[sc]sched.deferpool[sc] = d.linkd.link = nilpp.deferpool[sc] = append(pp.deferpool[sc], d)}

    GO语言中defer实现原理是什么

    点进去看池子的数据结构,其实里面的成员也就是 咱们之前说到的 _defer指针

    其中 sched.deferpool[sc] 是全局的池子,pp.deferpool[sc] 是本地的池子

    mallocgc分配空间

    上述操作若 d 没有拿到值,那么就直接使用 mallocgc 重新分配,且设置好 对应的成员 sizheap

    if d == nil {// Allocate new defer+args.systemstack(func() {total := roundupsize(totaldefersize(uintptr(siz)))d = (*_defer)(mallocgc(total, deferType, true))})}d.siz = sizd.heap = true

    mallocgc 具体实现在 src/runtime/malloc.go 中,若感兴趣的话,可以深入看看这一块,今天咱们不重点说这个函数

    // Allocate an object of size bytes.// Small objects are allocated from the per-P cache's free lists.// Large objects (> 32 kB) are allocated straight from the heap.func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {}

    最后再来看看return0

    最后再来看看 deferproc 函数中的 结果返回return0()

    // return0 is a stub used to return 0 from deferproc.// It is called at the very end of deferproc to signal// the calling Go function that it should not jump// to deferreturn.// in asm_*.sfunc return0()

    return0 是用于从deferproc返回0的存根

    它在deferproc函数的最后被调用,用来通知调用Go的函数它不应该跳转到deferreturn

    在正常情况下 return0 正常返回 0

    可是异常情况下 return0 函数会返回 1,此时GO 就会跳转到执行 deferreturn

    简单说下 deferreturn

    deferreturn的作用就是情况defer里面的链表,归还相应的缓冲区,或者把对应的空间让GC回收调

    GO 中 defer 的规则

    上面分析了GO 中defer 的实现原理之后,咱们现在来了解一下 GO 中应用defer 是需要遵守 3 个规则的,咱们来列一下:

    • defer后面跟的函数,叫延迟函数,函数中的参数在defer语句声明的时候,就已经确定下来了

    • 延迟函数的执行时按照后进先出来的,文章前面也多次说到过,这个印象应该很深刻吧,先出现的defer后执行,后出现的defer先执行

    • 延迟函数可能会影响到整个函数的返回值

    咱们还是要来解释一下的,上面第 2 点,应该都好理解,上面的图也表明了 执行顺序

    第一点咱们来写个小DEMO

    延迟函数中的参数在defer语句声明的时候,就已经确定下来了

    func main() {   num := 1   defer fmt.Println(num)   num++   return}

    别猜了,运行结果是 1,小伙伴们可以将代码拷贝下来,自己运行一波

    第三点也来一个DEMO

    延迟函数可能会影响到整个函数的返回值

    func test3() (res int) {   defer func() {      res++   }()   return 1}func main() {   fmt.Println(test3())   return}

    上述代码,我们在 test3函数中的返回值,我们提前命名好了,本来应该是返回结果为 1

    可是在return 这里,执行顺序这样的

    res = 1

    res++

    因此,结果就是 2

    关于“GO语言中defer实现原理是什么”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识,可以关注编程网精选频道,小编每天都会为大家更新不同的知识点。

    --结束END--

    本文标题: GO语言中defer实现原理是什么

    本文链接: https://lsjlt.com/news/349971.html(转载时请注明来源链接)

    有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

    猜你喜欢
    • GO语言中defer实现原理是什么
      这篇文章主要介绍“GO语言中defer实现原理是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“GO语言中defer实现原理是什么”文章能帮助大家解决问题。defer 是什么咱们一起来看看 def...
      99+
      2023-07-05
    • GO语言中defer实现原理的示例详解
      目录GO 中 defer的实现原理defer 是什么defer 实现原理GO 中 defer 的规则第一点咱们来写个小DEMO第三点也来一个DEMO总结GO 中 defer的实现原理...
      99+
      2023-02-24
      GO语言defer实现原理 GO语言defer原理 GO defer
    • GO语言中Chan的实现原理是什么
      今天小编给大家分享一下GO语言中Chan的实现原理是什么的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。chan 是什么是一种...
      99+
      2023-07-05
    • Go语言的make和new实现原理是什么
      这篇文章主要介绍“Go语言的make和new实现原理是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Go语言的make和new实现原理是什么”文章能帮助大家解决问题。概述虽然 make...
      99+
      2023-07-05
    • Go defer的实现原理剖析
      本篇内容介绍了“Go defer的实现原理剖析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1. 前言de...
      99+
      2024-04-02
    • Go语言中熔断的原理是什么
      本篇内容介绍了“Go语言中熔断的原理是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!今天我们就来一起看...
      99+
      2024-04-02
    • Go语言中defer语句怎么使用
      今天小编给大家分享一下Go语言中defer语句怎么使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。1.在一个函数内的def...
      99+
      2023-07-02
    • Go语言底层实现原理揭秘:底层语言究竟是什么?
      Go语言底层实现原理揭秘:底层语言究竟是什么? 在计算机科学领域中,底层语言通常指的是可以直接与硬件交互的编程语言,它可以更加精细地控制计算机的底层资源,包括内存、寄存器等。作为一种高...
      99+
      2024-03-07
      go语言 底层实现 原理揭秘
    • Go语言中goroutine的调度原理是什么
      Go语言中goroutine的调度原理是什么,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。一、关于并发的基础知识在讲gorou...
      99+
      2024-04-02
    • Go语言中数组索引和存储的实现原理是什么?
      Go语言是一门功能强大的编程语言,它拥有很多独特的特性,比如强类型、垃圾回收机制和原生支持并发等。其中,数组是Go语言中最基本的数据结构之一,它在很多场景下都有着广泛的应用。本文将介绍Go语言中数组索引和存储的实现原理。 数组的定义和初始...
      99+
      2023-11-06
      索引 数组 存储
    • GO语言中的缓存管理:容器框架的实现原理是什么?
      随着网络技术的不断发展,数据量的不断增加,对数据的快速响应成为了当今互联网应用的重要问题。在这个背景下,缓存技术作为一种优化手段越来越受到开发者的关注。而GO语言因为其高效性、简洁性和可扩展性,成为了越来越多开发者选择的语言。那么,在GO...
      99+
      2023-07-24
      容器 框架 缓存
    • go语言中slice,map,channl底层原理是什么
      今天小编给大家分享一下go语言中slice,map,channl底层原理是什么的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。...
      99+
      2023-06-30
    • go语言调用约定多返回值实现原理是什么
      这篇文章主要介绍了go语言调用约定多返回值实现原理是什么的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇go语言调用约定多返回值实现原理是什么文章都会有所收获,下面我们一起来看看吧。go简单代码反汇编用简单的代码...
      99+
      2023-06-30
    • Go channel实现原理是什么
      本篇内容主要讲解“Go channel实现原理是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Go channel实现原理是什么”吧!channel单纯地将函数并发执行是...
      99+
      2023-07-05
    • 解读C语言中Go语句的实现原理
      《C语言中Go语句的实现原理解析》 Go语句是Go语言中的一个关键字,用于实现并发执行任务。在C语言中,我们可以通过使用线程和多线程库来实现类似于Go语句的功能。本文将介绍C语言中如何...
      99+
      2024-04-02
    • c语言解释器的实现原理是什么
      C语言解释器的实现原理是将C语言源代码转换为可执行的机器代码并执行。下面是C语言解释器的基本实现原理:1. 词法分析:将源代码分解为...
      99+
      2023-08-08
      c语言
    • Go语言底层实现原理揭秘
      Go语言作为一种高效、简洁且易于学习的编程语言,受到了许多开发者的青睐。但是,作为使用者,了解底层实现原理往往能够让我们更好地应对各种情况,优化代码性能。本文将深入探讨Go语言的底层实...
      99+
      2024-03-13
      原理 go语言 底层实现
    • Go语言底层原理互斥锁的实现原理
      目录Go 互斥锁的实现原理?概念使用场景底层实现结构操作加锁解锁Go 互斥锁正常模式和饥饿模式的区别?正常模式(非公平锁)饥饿模式(公平锁)Go 互斥锁允许自旋的条件?Go 互斥锁的...
      99+
      2024-04-02
    • GO语言中Chan实现原理的示例详解
      目录GO 中 Chan 实现原理分享chan 是什么GO 中 Chan 的底层数据结构咱们来画个图看看dataqsiz 对应的环形队列是啥样的写 sendq和 读 recvq 等待队...
      99+
      2023-02-24
      GO语言Chan实现原理 GO语言Chan原理 GO语言Chan
    • GO语言中err接口及defer延迟异常怎么处理
      这篇“GO语言中err接口及defer延迟异常怎么处理”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“GO语言中err接口及d...
      99+
      2023-06-30
    软考高级职称资格查询
    编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
    • 官方手机版

    • 微信公众号

    • 商务合作