教程集 www.jiaochengji.com
教程集 >  Golang编程  >  golang教程  >  正文 golangdefer特性姿势还是有必要了解下的!!!

golangdefer特性姿势还是有必要了解下的!!!

发布时间:2022-03-04   编辑:jiaochengji.com
教程集为您提供golangdefer特性姿势还是有必要了解下的!!!等资源,欢迎您收藏本站,我们将为您提供最新的golangdefer特性姿势还是有必要了解下的!!!资源
<blockquote>


</blockquote>

[toc]

Golang 的 defer 是什么?通俗来讲就是延迟调用。defer 会在当前函数返回之前执行 defer 注册的函数。比如 defer func_x( )  这样语句会让你注册一个函数变量到 defer 的全局链表中,在 defer 语句所在的函数退出之前调用。

笔者使用一段时间 Golang 之后,对 Golang defer 的理解认为作用有两点:

<ol class="list-paddingleft-2"><li>panic 场景依然会被调用:这个是重要的一个特性,通常能简化我们的代码,确保无论任何场景,defer 的函数一定调用,通常用在锁或者资源的释放场景较多;</li><li>配套的两个行为代码可以放在最近的位置:创建&释放、加锁&放锁、前置&后置,使得代码更易读,编程体验优秀。最近的地方是哪里?下一行;</li></ol>

先看下 defer 的以下几个特性:

<h2>defer 的特性</h2>

我们先深入的剖析下 defer 具有的特性,知其然也。这些特性是需要我们记住的特点,才能更好的理解 defer 使用的场景。

<h3>延迟调用</h3><pre>package mainfunc main() { defer println("--- defer ---") println("--- end ---") }复制代码</pre>

defer 会在 main 函数 return 之前时候调用。核心要点:

<ol class="list-paddingleft-2"><li>延迟调用:defer 语句本身虽然是 main 的第一行,但是 fmt.Println 是先打印的;</li><li>defer 关键字一定是处于函数上下文:defer 必须放在函数内部;</li></ol><h3>LIFO</h3>

一个函数中有多个 defer 调用怎么办?压栈式执行,后入先出。

<pre>package mainimport ("strconv")func main() { for i := 1; i <= 6; i  { defer println("defer -->"   strconv.Itoa(i)) } println("--- end ---") }复制代码</pre>

压栈式执行,也就是说先注册的函数后调用。如上,我们注册的顺序式 1,2,3,4,5,6,最后打印 "--- end ---",所以执行的结果自然是反着来的,程序输出:

<pre>--- end --- defer -->6 defer -->5 defer -->4 defer -->3 defer -->2 defer -->1复制代码</pre><h3>作用域</h3>

要点:defer 和函数绑定。 两个理解,defer  只会和 defer  语句所在的特定函数绑定在一起,作用域也只在这个函数。 从语法上来讲,defer  语句也一定要在函数内,否则会报告语法错误。

<pre>package mainfunc main() { func() { defer println("--- defer ---") }() println("--- ending ---") }复制代码</pre>

如上,defer 处于一个匿名函数中,就 main 函数本身来讲,匿名函数 fun(){}() 先调用且返回,然后再调用 println("--- ending ---") ,所以程序输出自然是:

<pre>--- defer --- --- ending ---复制代码</pre><h3>异常场景</h3>

这个是个非常重要的特性:panic 也能执行。Golang 不鼓励异常的编程模式,但是却也留了 panic-recover 这个异常和捕捉异常的机制。所以 defer 机制就显得尤为重要,甚至可以说是必不可少的。因为你没有一个无视异常,永保调用的 defer 机制,很有可能就会发生各种资源泄露,死锁等场景。为什么?因为发生了 panic 却不代表进程一定会挂掉,很有可能被外层 recover 住。

<pre>package mainfunc main() { defer func() { if e := recover(); e != nil { println("--- defer ---") } }() panic("throw panic") }复制代码</pre>

如上,main 函数注册一个 defer ,且稍后主动触发 panic,main 函数退出之际就会调用 defer 注册的匿名函数。再提一点,这里其实有两个要点:

<ol class="list-paddingleft-2"><li>defer 在 panic 异常场景也能确保调用;</li><li>recover 必须和 defer 结合才有意义;</li></ol><h2>使用姿势</h2><h3>并发同步</h3>

以下的例子对两个并发的协程做了下同步控制,常规操作。

<pre>var wg sync.WaitGroupfor i := 0; i < 2; i  {     wg.Add(1)go func() {defer wg.Done()// 程序逻辑}() } wg.Wait()复制代码</pre><h3>锁场景</h3>

加锁解锁必须配套,在 Golang 有了 defer 之后,你就可以写了 lock 之后,立马就写 unlock ,这样就永远不会忘了。

<pre> mu.RLock() defer mu.RUnlock()复制代码</pre>

但是请注意,lock 以下的代码都会在锁内。所以下面的代码要足够精简和快速才行,如果说下面的逻辑很复杂,那么可能就需要手动控制 unlock 防止的位置了。

<h3>资源释放</h3>

某些资源是临时创建的,作用域只存在于现场函数中,用完之后需要销毁,这种场景也适用 defer 来释放。释放就在创建的下一行,这是个非常好的编程体验,这种编程方式能极大的避免资源泄漏。因为写了创建立马就可以写释放了,再也不会忘记了。

<pre>    // new 一个客户端 client;cli, err := clientv3.New(clientv3.Config{Endpoints: endpoints})if err != nil {         log.Fatal(err)     }// 释放该 client ,也就是说该 client 的声明周期就只在该函数中;defer cli.Close()复制代码</pre><h3>panic-recover</h3>

recover 必须和 defer 结合才行,使用姿势一般如下:

<pre>defer func() { if v := recover(); v != nil { _ = fmt.Errorf("PANIC=%v", v) } }()复制代码</pre><blockquote>

更多干货在,公众号:奇伢云存储

</blockquote><h2>总结</h2><ol class="list-paddingleft-2"><li>defer 其实并不是 Golang 独创,是多种高级语言的共同选择;</li><li>defer 最重要的一个特点就是无视异常可执行,这个是 Golang 在提供了 panic-recover 机制之后必须做的补偿机制;</li><li>defer 的作用域存在于函数,defer 也只有和函数结合才有意义;</li><li>defer 允许你把配套的两个行为代码放在最近相邻的两行,比如创建&释放、加锁&放锁、前置&后置,使得代码更易读,编程体验优秀;</li></ol>

本篇从 defer 的使用姿势入手,了解 defer 的特性,让大家知其然也。后续会从源码和实现的角度出发,梳理下 defer ,然后知其所以然也。


到此这篇关于“golangdefer特性姿势还是有必要了解下的!!!”的文章就介绍到这了,更多文章或继续浏览下面的相关文章,希望大家以后多多支持JQ教程网!

您可能感兴趣的文章:
golangdefer特性姿势还是有必要了解下的!!!
Golang错误和异常处理的正确姿势
CSS 阴影效果(无需图片即可实现)
使用html5可以干什么?Html5的优势和劣势(总结)
python爬虫可以自学吗
Photoshop给森林人像添加魔法施法场景效果
golang异常机制
Photoshop修图一人扮演两个或多个角色教程分享
php中加密解密DES的正确使用姿势
golang debug 配置_缓存击穿导致 golang 组件死锁的问题分享

[关闭]
~ ~