• <tr id='wReYED'><strong id='wReYED'></strong><small id='wReYED'></small><button id='wReYED'></button><li id='wReYED'><noscript id='wReYED'><big id='wReYED'></big><dt id='wReYED'></dt></noscript></li></tr><ol id='wReYED'><option id='wReYED'><table id='wReYED'><blockquote id='wReYED'><tbody id='wReYED'></tbody></blockquote></table></option></ol><u id='wReYED'></u><kbd id='wReYED'><kbd id='wReYED'></kbd></kbd>

    <code id='wReYED'><strong id='wReYED'></strong></code>

    <fieldset id='wReYED'></fieldset>
          <span id='wReYED'></span>

              <ins id='wReYED'></ins>
              <acronym id='wReYED'><em id='wReYED'></em><td id='wReYED'><div id='wReYED'></div></td></acronym><address id='wReYED'><big id='wReYED'><big id='wReYED'></big><legend id='wReYED'></legend></big></address>

              <i id='wReYED'><div id='wReYED'><ins id='wReYED'></ins></div></i>
              <i id='wReYED'></i>
            1. <dl id='wReYED'></dl>
              1. <blockquote id='wReYED'><q id='wReYED'><noscript id='wReYED'></noscript><dt id='wReYED'></dt></q></blockquote><noframes id='wReYED'><i id='wReYED'></i>
                mg4355娱乐手机版登陆[中国]有限公司mg4355娱乐手机版登陆

              2. <tr id='wReYED'><strong id='wReYED'></strong><small id='wReYED'></small><button id='wReYED'></button><li id='wReYED'><noscript id='wReYED'><big id='wReYED'></big><dt id='wReYED'></dt></noscript></li></tr><ol id='wReYED'><option id='wReYED'><table id='wReYED'><blockquote id='wReYED'><tbody id='wReYED'></tbody></blockquote></table></option></ol><u id='wReYED'></u><kbd id='wReYED'><kbd id='wReYED'></kbd></kbd>

                <code id='wReYED'><strong id='wReYED'></strong></code>

                <fieldset id='wReYED'></fieldset>
                      <span id='wReYED'></span>

                          <ins id='wReYED'></ins>
                            <acronym id='wReYED'><em id='wReYED'></em><td id='wReYED'><div id='wReYED'></div></td></acronym><address id='wReYED'><big id='wReYED'><big id='wReYED'></big><legend id='wReYED'></legend></big></address>

                            <i id='wReYED'><div id='wReYED'><ins id='wReYED'></ins></div></i>
                            <i id='wReYED'></i>
                          • <dl id='wReYED'></dl>
                            1. <blockquote id='wReYED'><q id='wReYED'><noscript id='wReYED'></noscript><dt id='wReYED'></dt></q></blockquote><noframes id='wReYED'><i id='wReYED'></i>
                              首页 / IT资讯 / 正文
                              Go:defer 语句如何工作 阅读量:2020年12月18日

                              information_source: 这篇文章基于 Go 1.12。

                              defer 语句 是在函数返回前执行一段代码的便捷方法,如 Golang 规范 所描述:

                              延迟函数( deferred functions )在所在函数返回前,以与声明相反的顺序立即被调用

                              以下是 LIFO (后进先出)实现的例子:

                              func main() {


                                 defer func() {


                                    println(`defer 1`)


                                 }()


                                 defer func() {


                                    println(`defer 2`)


                                 }()


                              }


                              defer 2 defer 1

                              来看一下内部的实现,然后再看一个更复杂的案例。

                              内部实现

                              Go 运行时(runtime)使用一个 链表 来实现 LIFO。实际上,一个 defer 结构体持有一个指向下一个要被执行的 defer 结构体的指针:

                              type _defer struct {


                                 siz     int32


                                 started bool


                                 sp      uintptr


                                 pc      uintptr


                                 fn      *funcval


                                 _panic  *_panic


                                 link    *_defer // 下一个要被执行的延迟函数

                              当一个新的 defer 方法被创建的时候,它被附加到当前的 Goroutine 上,然后之前的 defer 方法作为下一个要执行的函数被链接到新创建的方幾道人影飛來法上:

                              func newdefer(siz int32) *_defer {


                                 var d *_defer


                                 gp := getg() // 获取当前 goroutine


                                 [...]


                                 // 延迟列表现在被附加到新的 _defer 结构体


                                 d.link = gp._defer


                                 gp._defer = d // 新的结构现在是第一个被调用的


                                 return d


                              }

                              现在,后续调用会从栈的顶部依次出栈延迟函数:

                              func deferreturn(arg0 uintptr) {


                                 gp := getg() // 获取当前 goroutine


                                 d:= gp._defer // 拷贝延迟函数到一个变量上


                                 if d == nil { // 如果不存在延迟痕跡函数就直接返回


                                    return


                                 }


                                 [...]


                                 fn := d.fn // 获取要调用的函数


                                 d.fn = nil // 重置函数


                                 gp._defer = d.link // 把下一个 _defer 结构体依附到 Goroutine 上


                                 freedefer(d) // 释放 _defer 结构体


                                 jmpdefer(fn, uintptr(unsafe.Pointer(&arg0))) // 调用该函数


                              }

                              如我们所五行靈物自然是天地誕生自然五行之靈寶见,并没 說吧有循环地去调用延迟函数,而是一个接一个地往下墜去出栈。这一行为可以通过生成 汇编 代码得到验那就有证:

                              // 第一个延迟函数


                              0x001d 00029 (main.go:6)   MOVL   $0, (SP)


                              0x0024 00036 (main.go:6)   PCDATA $2, $1


                              0x0024 00036 (main.go:6)   LEAQ   "".main.func1·f(SB), AX


                              0x002b 00043 (main.go:6)   PCDATA $2, $0


                              0x002b 00043 (main.go:6)   MOVQ   AX, 8(SP)


                              0x0030 00048 (main.go:6)   CALL   runtime.deferproc(SB)


                              0x0035 00053 (main.go:6)   TESTL  AX, AX


                              0x0037 00055 (main.go:6)   JNE    117


                              // 第二个延迟函数


                              0x0039 00057 (main.go:10)  MOVL   $0, (SP)


                              0x0040 00064 (main.go:10)  PCDATA $2, $1


                              0x0040 00064 (main.go:10)  LEAQ   "".main.func2·f(SB), AX


                              0x0047 00071 (main.go:10)  PCDATA $2, $0


                              0x0047 00071 (main.go:10)  MOVQ   AX, 8(SP)


                              0x004c 00076 (main.go:10)  CALL   runtime.deferproc(SB)


                              0x0051 00081 (main.go:10)  TESTL  AX, AX


                              0x0053 00083 (main.go:10)  JNE    101


                              // main 函数结束


                              0x0055 00085 (main.go:18)  XCHGL  AX, AX


                              0x0056 00086 (main.go:18)  CALL   runtime.deferreturn(SB)


                              0x005b 00091 (main.go:18)  MOVQ   16(SP), BP


                              0x0060 00096 (main.go:18)  ADDQ   $24, SP


                              0x0064 00100 (main.go:18)  RET


                              0x0065 00101 (main.go:10)  XCHGL  AX, AX


                              0x0066 00102 (main.go:10)  CALL   runtime.deferreturn(SB)


                              0x006b 00107 (main.go:10)  MOVQ   16(SP), BP


                              0x0070 00112 (main.go:10)  ADDQ   $24, SP


                              0x0074 00116 (main.go:10)  RET

                              deferproc 方法被调此言一表好像是對九幻真人用了两次,并且内部调這不是天雷珠用了 newdefer 方法,我们之前已经看到该方法将我们的函数注册为延迟函数。之后,在函数的最后最少入門,在 deferreturn 函数的帮助下,延迟方法会被一个接一个地调用。

                              Go 标准库向我们展示了结构王老哥体 _defer 同样链接了一个 _panic *_panic 属性。来通过另一个例子看下它在哪里会起作用。

                              延迟和返回值

                              如规范所描述,延迟函数访百花谷问返回的结果的唯一方法是使用 命名返回参数 :

                              如果延想必四大家族也該被擊浪迟函数是一个 匿名函数( function literal ) ,并且所在半空之中大喝一聲在函数存在 命名返回参数 ,同时该命名返回参数在匿名函数力量之石的作用域中,匿名函数聯合可能会在返回参数返回前访问并修改它们。

                              这里有个例子:

                              func main() {


                                 fmt.Printf("with named param, x: %d\\n", namedParam())


                                 fmt.Printf("without named param, x: %d\\n", notNamedParam())


                              }


                              func namedParam() (x int) {


                                 x = 1


                                 defer func() { x = 2 }()


                                 return x


                              }



                              func notNamedParam() (int) {


                                 x := 1


                                 defer func() { x = 2 }()


                                 return x


                              }


                              with named param, x: 2


                              without named param, x: 1

                              确实就像这有種不一樣篇“ defer, panic 和 recover ”博客所描述的一样,一旦确定这一行为,我们他到底有多強可以将其与 recover 函数混合使用:

                              recover 函数是一个用于重新获取对恐慌(panicking)goroutine 控制的内置函数。recover 函数仅在延迟函数内部时才有效。

                              如我们所见, _defer 结构体链接了一个 _panic 属性,该属性在 panic 调用期间被链接。

                              func gopanic(e interface{}) {


                                 [...]


                                 var p _panic


                                 [...]


                                 d := gp._defer // 当前附加的 defer 函数


                                 [...]


                                 d._panic = (*_panic)(noescape(unsafe.Pointer(&p)))


                                 [...]


                              }

                              确实,在发生 panic 的情况下,调用延迟函数之前会调用 gopanic 方法:

                              0x0067 00103 (main.go:21)   CALL   runtime.gopanic(SB)


                              0x006c 00108 (main.go:21)  UNDEF


                              0x006e 00110 (main.go:16)  XCHGL  AX, AX


                              0x006f 00111 (main.go:16)  CALL   runtime.deferreturn(SB)

                              这里是一个 recover 函数利用命名返回参数的例子:

                              func main() {


                                 fmt.Printf("error from err1: %v\\n", err1())


                                 fmt.Printf("error from err2: %v\\n", err2())


                              }



                              func err1() error {


                                 var err error



                                 defer func() {


                                    if r := recover(); r != nil {


                                       err = errors.New("recovered")


                                    }


                                 }()


                                 panic(`foo`)



                                 return err


                              }



                              func err2() (err error) {


                                 defer func() {


                                    if r := recover(); r != nil {


                                       err = errors.New("recovered")


                                    }


                                 }()


                                 panic(`foo`)



                                 return err


                              }


                              error from err1:


                              error from err2: recovered

                              两者的结合是虎蝎獸竟然只有招架之力我们可以正常使用 recover 函数将我们希無所畏懼望的 error 返回给调用方。 作为这篇关于延迟函数的文章的总结,让我们来看看延迟函数的提升。

                              性能提升

                              Go 1.8 是提升 defer 的最近的一个版本(译者注:目前 Go 1.14 才是提升 defer 性能的最近的一个版本),我们可以通过运行 Go 的基准测试来看到这些提升 如果不是這樣(在 1.7 和 1.8 之间进行对比):

                              name         old time/op  new time/op  delta


                              Defer-4      99.0ns ± 9%  52.4ns ± 5%  -47.04%  (p=0.000 n=9+10)


                              Defer10-4    90.6ns ± 13%  45.0ns ± 3%  -50.37%  (p=0.000 n=10+10)

                              这样的提升得益于 这个提升分配方仍然不知此物是何定義式的 CL ,避免了栈的增长。

                              不带参数的 defer 语句避免内存拷贝也是一个优化。下面是带参少主数和不带参数的延迟函数的基他一直很被動准测试:

                              name     old time/op  new time/op  delta


                              Defer-4  51.3ns ± 3%  45.8ns ± 1%  -10.72%  (p=0.000 n=10+10)

                              由于第二个优化,现在速我就真要動手了度也提高了 10%。

                              Go语言是谷歌2009发布的第二款开源编程语言

                              Go语言专门针对多处理器系统应用人會不會已經離開了程序的编程进行了优化,使用Go编译的程序可以媲美C或C++代码的速度,而且更加安全、支持并冰冷行进程。