教程集 www.jiaochengji.com
教程集 >  Golang编程  >  golang教程  >  正文 go使用Defer的几个场景

go使用Defer的几个场景

发布时间:2021-12-03   编辑:jiaochengji.com
教程集为您提供go使用Defer的几个场景等资源,欢迎您收藏本站,我们将为您提供最新的go使用Defer的几个场景资源

Go 语言中的 <code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">defer</code> 语句是 UNIX 之父 <code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">Ken Thompson</code> 大神发明的, 是完全正交的设计.

也正因为 Go 语言遵循的是正交的设计, 所以才有了: “少是指数级的多/Less is exponentially more” 的说法. 因为是正交的设计, 最终得到的组合形式是指数级的组合形式.

相反, C 的特性虽然很多, 但是很多不是正交的设计, 而只是简单的特性罗列, 
所以C 的很多地方是无法达到指数级的多的组合方式的. 但是学习成本却非常高.

简单的例子就是C 的构造函数和析构函数和C语言的函数和<code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">struct</code>完全是互斥的. 
具体的例子可以参考: C 去掉构造函数会怎么样?

关于 Go 语言中 <code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">defer</code> 语句的详细介绍请参考: Defer, Panic, and Recover .

C 中模拟的 <code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">defer</code> 实现请参考: C 版的defer语句 .

这里主要是总结 <code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">defer</code> 语句的一些使用场景.


<span style="padding:0px; margin:0px">1. 简化资源的回收</span>

这是最常见的 <code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">defer</code> 用法. 比如:

<pre style="padding:5px; margin-top:0px; margin-bottom:0px; line-height:18px; font-size:9pt; font-family:'Courier New',Arial; border:1px solid rgb(221,221,221); background:rgb(246,246,246)"><code class="hljs stylus" style="padding:8px 5px; margin:0px 2px; display:block; overflow-x:auto; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'; white-space:pre-wrap; word-wrap:break-word; word-break:break-all; background:rgb(255,255,255)">mu.<span class="hljs-function" style="padding:0px; margin:0px"><span class="hljs-title" style="padding:0px; margin:0px">Lock</span><span class="hljs-params" style="padding:0px; margin:0px">()</span></span> defer mu.<span class="hljs-function" style="padding:0px; margin:0px"><span class="hljs-title" style="padding:0px; margin:0px">Unlock</span><span class="hljs-params" style="padding:0px; margin:0px">()</span></span> </code></pre>

当然, <span style="color:#ff0000"><code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">defer</code> 也有一定的开销, 也有为了节省性能而回避使用的 <code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">defer</code> 的</span>:

<pre style="padding:5px; margin-top:0px; margin-bottom:0px; line-height:18px; font-size:9pt; font-family:'Courier New',Arial; border:1px solid rgb(221,221,221); background:rgb(246,246,246)"><code class="hljs stylus" style="padding:8px 5px; margin:0px 2px; display:block; overflow-x:auto; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'; white-space:pre-wrap; word-wrap:break-word; word-break:break-all; background:rgb(255,255,255)">mu.<span class="hljs-function" style="padding:0px; margin:0px"><span class="hljs-title" style="padding:0px; margin:0px">Lock</span><span class="hljs-params" style="padding:0px; margin:0px">()</span></span> count mu.<span class="hljs-function" style="padding:0px; margin:0px"><span class="hljs-title" style="padding:0px; margin:0px">Unlock</span><span class="hljs-params" style="padding:0px; margin:0px">()</span></span> </code></pre>

从简化资源的释放角度看, <code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">defer</code> 类似一个语法糖, 好像不是必须的.


<span style="padding:0px; margin:0px">2. <code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">panic</code>异常的捕获</span>

<code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">defer</code> 除了用于简化资源的释放外, 还是Go语言异常框架的一个组成部分.

Go语言中, <span style="color:#ff0000"><code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">panic</code>用于抛出异常, <code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">recover</code>用于捕获异常. </span>

<span style="color:#ff0000"><code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">recover</code>只能在<code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">defer</code>语句中使用, 直接调用<code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">recover</code>是无效的</span>.

比如:

<pre style="padding:5px; margin-top:0px; margin-bottom:0px; line-height:18px; font-size:9pt; font-family:'Courier New',Arial; border:1px solid rgb(221,221,221); background:rgb(246,246,246)"><code class="hljs swift" style="padding:8px 5px; margin:0px 2px; display:block; overflow-x:auto; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'; white-space:pre-wrap; word-wrap:break-word; word-break:break-all; background:rgb(255,255,255)"><span class="hljs-func" style="padding:0px; margin:0px"><span class="hljs-keyword" style="padding:0px; margin:0px; font-weight:700">func</span> <span class="hljs-title" style="padding:0px; margin:0px">main</span><span class="hljs-params" style="padding:0px; margin:0px">()</span> </span>{ f() fmt.<span class="hljs-type" style="padding:0px; margin:0px; font-weight:700">Println</span>(<span class="hljs-string" style="padding:0px; margin:0px; font-weight:700">"Returned normally from f."</span>) } <span class="hljs-func" style="padding:0px; margin:0px"><span class="hljs-keyword" style="padding:0px; margin:0px; font-weight:700">func</span> <span class="hljs-title" style="padding:0px; margin:0px">f</span><span class="hljs-params" style="padding:0px; margin:0px">()</span> </span>{ <span style="color:#ff0000">defer</span> <span class="hljs-func" style="padding:0px; margin:0px"><span class="hljs-keyword" style="padding:0px; margin:0px; font-weight:700">func</span><span class="hljs-params" style="padding:0px; margin:0px">()</span> </span>{ <span class="hljs-keyword" style="padding:0px; margin:0px; font-weight:700">if</span> r := <span style="color:#ff0000">recover</span>(); r != <span class="hljs-built_in" style="padding:0px; margin:0px">nil</span> { fmt.<span class="hljs-type" style="padding:0px; margin:0px; font-weight:700">Println</span>(<span class="hljs-string" style="padding:0px; margin:0px; font-weight:700">"Recovered in f"</span>, r) } }() fmt.<span class="hljs-type" style="padding:0px; margin:0px; font-weight:700">Println</span>(<span class="hljs-string" style="padding:0px; margin:0px; font-weight:700">"Calling g."</span>) g() fmt.<span class="hljs-type" style="padding:0px; margin:0px; font-weight:700">Println</span>(<span class="hljs-string" style="padding:0px; margin:0px; font-weight:700">"Returned normally from g."</span>) } <span class="hljs-func" style="padding:0px; margin:0px"><span class="hljs-keyword" style="padding:0px; margin:0px; font-weight:700">func</span> <span class="hljs-title" style="padding:0px; margin:0px">g</span><span class="hljs-params" style="padding:0px; margin:0px">()</span> </span>{ panic(<span class="hljs-string" style="padding:0px; margin:0px; font-weight:700">"ERROR"</span>) } </code></pre>

因此, 如果要捕获Go语言中函数的异常, 就离不开<code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">defer</code>语句了.

<span style="padding:0px; margin:0px">3. 修改返回值</span>

<code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">defer</code> 除了用于配合 <code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">recover</code>, 用于捕获 <code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">panic</code> 异常外,<span style="color:#ff0000"><span style="font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'; font-size:12.5px; letter-spacing:0.5px; line-height:22.5px">defer</span>还可以用于在 <code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">return</code> 之后修改函数的返回值.</span>

比如:

<pre style="padding:5px; margin-top:0px; margin-bottom:0px; line-height:18px; font-size:9pt; font-family:'Courier New',Arial; border:1px solid rgb(221,221,221); background:rgb(246,246,246)"><code class="hljs swift" style="padding:8px 5px; margin:0px 2px; display:block; overflow-x:auto; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'; white-space:pre-wrap; word-wrap:break-word; word-break:break-all; background:rgb(255,255,255)"><span class="hljs-func" style="padding:0px; margin:0px"><span class="hljs-keyword" style="padding:0px; margin:0px; font-weight:700">func</span> <span class="hljs-title" style="padding:0px; margin:0px">doubleSum</span><span class="hljs-params" style="padding:0px; margin:0px">(a, b int)</span> <span class="hljs-params" style="padding:0px; margin:0px">(sum int)</span> </span>{ <span style="font-weight:bold"><span style="color:#ff0000">defer <span class="hljs-func" style="padding:0px; margin:0px"><span class="hljs-keyword" style="padding:0px; margin:0px">func</span><span class="hljs-params" style="padding:0px; margin:0px">()</span> </span>{ //该函数在函数返回时 调用 sum *= <span class="hljs-number" style="padding:0px; margin:0px">2</span> }()</span></span> sum = a b } </code></pre>

当然, 这个特性应该只是 <code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">defer</code> 的副作用, 具体在什么场景使用就要由开发者自己决定了.

<span style="padding:0px; margin:0px">
</span>

<span style="padding:0px; margin:0px">4. 安全的回收资源</span>

前面第一点提到, <code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">defer</code> 最常见的用法是简化资源的回收. 而且, 从资源回收角度看, 
<code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">defer</code> 只是一个语法糖.

其实, 也不完全是这样, 特别是在涉及到第二点提到的<code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">panic</code>异常等因素导致<code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">goroutine</code>提前退出时.

比如, 有一个线程安全的slice修改函数, 为了性能没有使用<code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">defer</code>语句:

<pre style="padding:5px; margin-top:0px; margin-bottom:0px; line-height:18px; font-size:9pt; font-family:'Courier New',Arial; border:1px solid rgb(221,221,221); background:rgb(246,246,246)"><code class="hljs sql" style="padding:8px 5px; margin:0px 2px; display:block; overflow-x:auto; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'; white-space:pre-wrap; word-wrap:break-word; word-break:break-all; background:rgb(255,255,255)">func <span class="hljs-operator" style="padding:0px; margin:0px"><span class="hljs-keyword" style="padding:0px; margin:0px; font-weight:700">set</span>(mu *sync.<span class="hljs-keyword" style="padding:0px; margin:0px; font-weight:700">Mutex</span>, arr []<span class="hljs-built_in" style="padding:0px; margin:0px">int</span>, i, v <span class="hljs-built_in" style="padding:0px; margin:0px">int</span>) { mu.<span class="hljs-keyword" style="padding:0px; margin:0px; font-weight:700">Lock</span>() arr[i] = v mu.<span class="hljs-keyword" style="padding:0px; margin:0px; font-weight:700">Unlock</span>() } </span></code></pre>

但是, 如果 <code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">i >= len(arr)</code>的话, <code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">runtime</code>就会抛出切片越界的异常(这里只是举例, 实际开发中不应该出现切片越界异常). 这样的话, <code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">mu.Unlock()</code> 就没有机会被执行了.

如果用<code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">defer</code>的话, 即使出现异常也能保证<code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">mu.Unlock()</code>被调用:

<pre style="padding:5px; margin-top:0px; margin-bottom:0px; line-height:18px; font-size:9pt; font-family:'Courier New',Arial; border:1px solid rgb(221,221,221); background:rgb(246,246,246)"><code class="hljs sql" style="padding:8px 5px; margin:0px 2px; display:block; overflow-x:auto; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'; white-space:pre-wrap; word-wrap:break-word; word-break:break-all; background:rgb(255,255,255)">func <span class="hljs-operator" style="padding:0px; margin:0px"><span class="hljs-keyword" style="padding:0px; margin:0px; font-weight:700">set</span>(mu *sync.<span class="hljs-keyword" style="padding:0px; margin:0px; font-weight:700">Mutex</span>, arr []<span class="hljs-built_in" style="padding:0px; margin:0px">int</span>, i, v <span class="hljs-built_in" style="padding:0px; margin:0px">int</span>) { mu.<span class="hljs-keyword" style="padding:0px; margin:0px; font-weight:700">Lock</span>() defer mu.<span class="hljs-keyword" style="padding:0px; margin:0px; font-weight:700">Unlock</span>() arr[i] = v } </span></code></pre>

当然, Go语言约定异常不会跨越<code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">package</code>边界. 因此, 调用一般函数的时候不用担心<code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">goroutine</code>异常退出的情况.

不过对于一些比较特殊的<code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">package</code>, 比如<code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">go test</code>依赖的<code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">testing</code>包, 包中的<code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">t.Fatal</code>就是依赖了Go中类似异常的特性(准确的说是调用了<code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">runtime.Goexit()</code>).

比如有以下的测试函数(详情请参考Issue5746):

<pre style="padding:5px; margin-top:0px; margin-bottom:0px; line-height:18px; font-size:9pt; font-family:'Courier New',Arial; border:1px solid rgb(221,221,221); background:rgb(246,246,246)"><code class="hljs swift" style="padding:8px 5px; margin:0px 2px; display:block; overflow-x:auto; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'; white-space:pre-wrap; word-wrap:break-word; word-break:break-all; background:rgb(255,255,255)"><span class="hljs-func" style="padding:0px; margin:0px"><span class="hljs-keyword" style="padding:0px; margin:0px; font-weight:700">func</span> <span class="hljs-title" style="padding:0px; margin:0px">TestFailed</span><span class="hljs-params" style="padding:0px; margin:0px">(t *testing.T)</span> </span>{ <span class="hljs-keyword" style="padding:0px; margin:0px; font-weight:700">var</span> wg sync.<span class="hljs-type" style="padding:0px; margin:0px; font-weight:700">WaitGroup</span> <span class="hljs-keyword" style="padding:0px; margin:0px; font-weight:700">for</span> i := <span class="hljs-number" style="padding:0px; margin:0px">0</span>; i < <span class="hljs-number" style="padding:0px; margin:0px">2</span>; i { wg.<span class="hljs-type" style="padding:0px; margin:0px; font-weight:700">Add</span>(<span class="hljs-number" style="padding:0px; margin:0px">1</span>) go <span class="hljs-func" style="padding:0px; margin:0px"><span class="hljs-keyword" style="padding:0px; margin:0px; font-weight:700">func</span><span class="hljs-params" style="padding:0px; margin:0px">(id int)</span> </span>{ <span class="hljs-comment" style="padding:0px; margin:0px; font-style:italic">// defer wg.Done()</span> t.<span class="hljs-type" style="padding:0px; margin:0px; font-weight:700">Fatalf</span>(<span class="hljs-string" style="padding:0px; margin:0px; font-weight:700">"TestFailed: id = %v\n"</span>, id) wg.<span class="hljs-type" style="padding:0px; margin:0px; font-weight:700">Done</span>() }(i) } wg.<span class="hljs-type" style="padding:0px; margin:0px; font-weight:700">Wait</span>() } </code></pre>

当测试失败的时候, <code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">wg.Done()</code>将没有机会执行, 最终导致<code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">wg.Wait()</code>死锁.

对于这个例子, 安全的做法是使用<code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">defer</code>语句保证<code style="padding:0px; margin:0px; font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,'monospace !important'">wg.Done()</code>始终会被执行.

转自:http://my.oschina.net/chai2010/blog/140065


#######################################

本文实例讲述了GO语言延迟函数defer用法。分享给大家供大家参考。具体分析如下:

<span style="white-space:pre"/><span style="color:rgb(255,0,0)">defer 在声明时不会立即执行,而是在函数 return 后,再按照 FILO (先进后出)的原则依次执行每一个 defer.</span>

<span style="white-space:pre"><span style="line-height:25.2px">defer</span></span>一般用于异常处理、释放资源、清理数据、记录日志等。这有点像面向对象语言的析构函数,优雅又简洁,是 Golang 的亮点之一。

代码1:了解 defer 的执行顺序

<span style="line-height:21.6px; font-size:12px; float:right"><u>复制代码</u></span>代码如下:
package main

import "fmt"

func fn(n int) int {
 defer func() {
  n
  fmt.Println("3st:", n)
 }()

 defer func() {
  n
  fmt.Println("2st:", n)
 }()

 defer func() {
  n
  fmt.Println("1st:", n)
 }()

 return n //没有做任何事情
}

func main() {
 fmt.Println("函数返回值:", fn(0))
}


<span style="color:rgb(51,51,51); font-size:14px; font-family:tahoma,arial,宋体; line-height:25.2px">输出:</span><span style="color:rgb(51,51,51); font-family:Arial; font-size:14px; line-height:26px"/>

1st: 1
2st: 2
3st: 3
函数返回值: 0

代码2:经典应用实例

<span style="line-height:21.6px; font-size:12px; float:right"><u>复制代码</u></span>代码如下:
func CopyFile(dst, src string) (w int64, err error) {
 srcFile, err := os.Open(src)
 if err != nil {
  return
 }
<span style="color:rgb(255,0,0)"> defer srcFile.Close() </span>//每次申请资源时,请习惯立即申请一个 defer 关闭资源,这样就不会忘记释放资源了

 dstFile, err := os.Create(dst)
 if err != nil {
  return
 }
 defer dstFile.Close()

 return io.Copy(dstFile, srcFile)
}


<h3 style="margin:0px; padding:0px; color:rgb(51,51,51); font-family:Arial; line-height:26px"> <span style="font-family:tahoma,arial,宋体; font-size:14px; line-height:25.2px">defer 还有一个重要的特性,就是</span><span style="line-height:21.6px; font-family:tahoma,arial,宋体"><span style="color:rgb(255,0,0)">即便函数抛出了异常,也会被执行的。 这样就不会因程序出现了错误,而导致资源不会释放了</span></span><span style="line-height:21.6px; font-family:tahoma,arial,宋体; color:rgb(0,0,255)">。</span></h3>



到此这篇关于“go使用Defer的几个场景”的文章就介绍到这了,更多文章或继续浏览下面的相关文章,希望大家以后多多支持JQ教程网!

您可能感兴趣的文章:
golangdefer特性姿势还是有必要了解下的!!!
defer ,panic,recover
Go 异常处理机制——defer, error, panic, recover
使用 defer 还是不使用 defer?
Golang Defer详解
Golang defer解读
photoshop合成鲸鱼城堡童话场景制作教程
[翻译]Go的Defer、Panic和Recover
详解defer实现机制(附上三道面试题)
Go 语言中的错误处理机制

上一篇:golang切片传参 下一篇:GO接口详解
[关闭]
~ ~