Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >深入理解 Go 中的 defer、panic 、日志管理与WebAssembly

深入理解 Go 中的 defer、panic 、日志管理与WebAssembly

作者头像
蒙娜丽宁
发布于 2024-11-23 07:44:06
发布于 2024-11-23 07:44:06
8800
代码可运行
举报
文章被收录于专栏:极客起源极客起源
运行总次数:0
代码可运行

延迟执行 (defer) 关键字的使用

在 Go 语言中,defer 关键字用于推迟某个函数的执行,直到其所在的外层函数即将返回时才执行。这在文件输入输出操作中非常有用,因为它允许你在打开文件后直接将关闭文件的操作放在附近,从而避免忘记关闭文件。defer 可以让你的代码更加简洁、可读。虽然在后续章节中我们将讨论 defer 在文件操作中的应用,本文先介绍 defer 在其他场景中的两种用法。

defer 的执行顺序

一个非常重要的点是,defer 语句会按照后进先出的顺序(LIFO)执行。这意味着,如果你在同一个函数中依次 deferf1()f2()f3(),那么在函数返回时,f3() 将会先执行,接着是 f2(),最后是 f1()

为了更好地理解 defer 的工作机制,下面是一个简单的 Go 代码示例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main import (     "fmt" )  func d1() {     for i := 3; i > 0; i-- {         defer fmt.Print(i, " ")     } } 

除了 import 块外,上面的代码实现了一个名为 d1() 的函数,其中包含一个 for 循环和一个 defer 语句。defer 将会在循环体内执行三次。

接下来是程序的第二部分:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func d2() {     for i := 3; i > 0; i-- {         defer func() {             fmt.Print(i, " ")         }()     }     fmt.Println() } 

在这个部分的代码中,你可以看到另一个名为 d2() 的函数实现。它同样包含一个 for 循环和一个 defer 语句,但这次 defer 应用于一个匿名函数,而不是直接调用 fmt.Print()。匿名函数没有参数,因此每次循环都会捕获 i 的当前值。

最后一部分代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func d3() {     for i := 3; i > 0; i-- {         defer func(n int) {             fmt.Print(n, " ")         }(i)     } }  func main() {     d1()     d2()     fmt.Println()     d3()     fmt.Println() } 

在这个部分,main() 函数调用了 d1()d2()d3() 函数。在 d3() 中,匿名函数带有一个参数 n,并且在每次 defer 时,将 i 的当前值传递给了该匿名函数。执行整个程序时,输出如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
1 2 3  0 0 0  1 2 3 

你可能觉得这个输出很难理解,因为 defer 的操作和结果可能有些让人迷惑。我们来解释一下这些输出,以帮助你更好地理解。

结果分析

首先,输出的第一行 1 2 3 是由 d1() 函数生成的。在 d1() 中,i 的值按顺序是 3、2、1,但由于 defer 的执行顺序是 LIFO,因此在 d1() 返回时,值按相反顺序输出。

接下来是由 d2() 生成的第二行输出 0 0 0。为什么不是 1 2 3?原因在于,for 循环结束时,i 的值为 0,而匿名函数是在 for 循环结束后才执行的,因此 i 的值为 0 时,匿名函数被执行了三次,结果是三个 0。

最后,第三行 1 2 3 是由 d3() 生成的。因为匿名函数带有参数 n,每次 deferi 的值会被传递给匿名函数,因此 defer 的匿名函数捕获了不同的 i 值,输出了正确的顺序 1 2 3

因此,最好的 defer 使用方法是像 d3() 那样,通过显式传递所需的参数来避免混淆。

日志中的 defer 使用

defer 还可以应用于日志记录,帮助你在程序中更好地组织日志信息。通过在函数开头和返回前分别记录开始和结束日志,你可以确保所有日志输出都是成对的。这样可以让日志信息更加清晰,易于查找。

例如,以下代码展示了如何使用 defer 记录函数的开始和结束日志:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main import (     "fmt"     "log"     "os" )  var LOGFILE = "/tmp/mGo.log"  func one(aLog *log.Logger) {     aLog.Println("-- 函数 one 开始 --")     defer aLog.Println("-- 函数 one 结束 --")     for i := 0; i < 10; i++ {         aLog.Println(i)     } } 

这个 one() 函数使用了 defer,确保第二个 aLog.Println() 在函数返回前被执行,因此日志输出会被封装在两个日志调用之间,使得日志信息更具可读性。

接下来是另一个类似的函数 two()

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func two(aLog *log.Logger) {     aLog.Println("---- 函数 two 开始 ----")     defer aLog.Println("-- 函数 two 结束 --")     for i := 10; i > 0; i-- {         aLog.Println(i)     } } 

two() 函数也使用了 defer 来组织日志信息,这次的日志内容略有不同,但原理相同。

最后,我们看看 main() 函数的实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func main() {     f, err := os.OpenFile(LOGFILE, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)     if err != nil {         fmt.Println(err)         return     }     defer f.Close()     iLog := log.New(f, "logDefer ", log.LstdFlags)     iLog.Println("程序开始!")     one(iLog)     two(iLog)     iLog.Println("程序结束!") } 

这里,我们打开了一个日志文件,并使用 defer 确保文件在程序结束时被关闭。运行这个程序并查看日志文件的内容,你会发现以下输出:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
logDefer 2019/01/19 21:15:11 -- 函数 one 开始 -- logDefer 2019/01/19 21:15:11 0 logDefer 2019/01/19 21:15:11 1 ... logDefer 2019/01/19 21:15:11 -- 函数 one 结束 -- logDefer 2019/01/19 21:15:11 ---- 函数 two 开始 ---- logDefer 2019/01/19 21:15:11 10 logDefer 2019/01/19 21:15:11 9 ... logDefer 2019/01/19 21:15:11 -- 函数 two 结束 -- 

这样,通过 defer,日志信息可以成对显示,使日志更加清晰,便于调试。

panicrecover

接下来,我们讨论一个稍微复杂点的机制:panic()recover()panic() 是 Go 语言中的内建函数,它会中断当前程序的正常执行,并进入恐慌状态。而 recover() 则允许你在发生恐慌后重新获得控制权。

以下是一个展示这两者使用的示例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main import "fmt"  func a() {     fmt.Println("进入 a()")     defer func() {         if c := recover(); c != nil {             fmt.Println("在 a() 中恢复!")         }     }()     fmt.Println("即将调用 b()")     b()     fmt.Println("b() 已退出!") }  func b() {     fmt.Println("进入 b()")     panic("b() 中的恐慌!") }  func main() {     a()     fmt.Println("main() 已结束!") } 

运行这段代码会得到以下输出:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
进入 a() 即将调用 b() 进入 b()a() 中恢复! main() 已结束! 

在这个例子中,b() 中调用了 panic(),但由于 a() 中有一个 recover(),程序得以从恐慌中恢复,并且继续执行剩下的代码。

使用 panic() 处理错误

在某些情况下,你可能只想使用 panic() 来强制终止程序。以下代码

展示了这种情况:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main import (     "fmt"     "os" )  func main() {     if len(os.Args) == 1 {         panic("参数不足!")     }     fmt.Println("感谢提供参数!") } 

当没有提供命令行参数时,程序将输出以下内容并中止:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
panic: 参数不足! 

panic() 是一种直接处理错误的方式,但请记住,如果不使用 recover()panic() 会使程序立即崩溃。

UNIX 调试工具

当程序出现问题时,有时我们不希望通过修改代码来添加大量的调试信息。这时可以借助 UNIX 下的工具,如 stracedtrace,来跟踪程序的系统调用并找出问题所在。

strace 工具

strace 是一个用于跟踪 Linux 系统中系统调用和信号的工具。你可以使用它来查看某个程序在运行时所执行的系统调用。例如,运行 strace ls 会输出如下内容:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
execve("/bin/ls", ["ls"], [/* 15 vars */]) = 0 
dtrace 工具

dtrace 是 macOS 和 FreeBSD 系统中的另一个强大工具,允许你监视系统中正在运行的程序而无需修改代码。例如,使用 dtruss godoc 命令可以跟踪 godoc 程序的系统调用。

检查 Go 语言环境

Go 语言提供了 runtime 包,用于查看当前 Go 环境的信息。以下代码展示了如何使用 runtime 获取系统信息:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main import (     "fmt"     "runtime" )  func main() {     fmt.Println("使用的编译器:", runtime.Compiler)     fmt.Println("系统架构:", runtime.GOARCH)     fmt.Println("Go 语言版本:", runtime.Version())     fmt.Println("CPU 数量:", runtime.NumCPU())     fmt.Println("当前 Goroutines 数量:", runtime.NumGoroutine()) } 

运行这段代码,你可以得到当前使用的编译器、系统架构、Go 版本等信息。

WebAssembly 的生成与使用

Go 支持将代码编译为 WebAssembly(Wasm),这是一种面向虚拟机的高效执行格式,适用于多种平台。以下是一个简单的 Go 代码示例,它将会被编译为 WebAssembly:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main import (     "fmt" )  func main() {     fmt.Println("生成 WebAssembly 代码!") } 

使用以下命令将其编译为 WebAssembly:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ GOOS=js GOARCH=wasm go build -o main.wasm toWasm.go 

生成的 main.wasm 文件可以在支持 WebAssembly 的浏览器中运行。你还需要加载 wasm_exec.js 文件,来帮助浏览器运行 WebAssembly。

以下是一个简单的 index.html 文件,包含用于加载和运行 WebAssembly 的代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<!DOCTYPE html> <html> <head>     <meta charset="utf-8">     <title>Go 和 WebAssembly</title> </head> <body>     <script src="wasm_exec.js"></script>     <script>         const go = new Go();         WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject).then((result) => {             go.run(result.instance);         });     </script> </body> </html> 

编写高质量的 Go 代码的建议

本文最后总结了一些实用的建议,帮助你编写高质量的 Go 代码:

  1. 当函数中出现错误时,要么记录错误,要么返回错误,不要同时做这两件事,除非有特殊理由。
  2. Go 接口定义的是行为,而不是数据。
  3. 使用 io.Readerio.Writer 接口,使代码更具扩展性。
  4. 只有在必要时才传递变量的指针,其他时候直接传递值。
  5. 错误类型不是字符串,它是 error 类型。
  6. 不要在生产环境中测试代码,除非有特殊理由。
  7. 如果不熟悉某个 Go 特性,先做测试再用,尤其是大规模应用时。
  8. 不要害怕犯错,尽量多做实验,实践是最好的学习方式。

- EOF -

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-09-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 极客起源 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Golang 高效实践之defer、panic、recover实践
我们知道Golang处理异常是用error返回的方式,然后调用方根据error的值走不同的处理逻辑。但是,如果程序触发其他的严重异常,比如说数组越界,程序就要直接崩溃。Golang有没有一种异常捕获和恢复机制呢?这个就是本文要讲的panic和recover。其中recover要配合defer使用才能发挥出效果。
用户2937493
2019/08/29
9810
Golang 高效实践之defer、panic、recover实践
Go 函数的健壮性、panic异常处理、defer 机制
函数的使用者可能是任何人,这些人在使用函数之前可能都没有阅读过任何手册或文档,他们会向函数传入你意想不到的参数。因此,为了保证函数的健壮性,函数需要对所有输入的参数进行合法性的检查。一旦发现问题,立即终止函数的执行,返回预设的错误值。
贾维斯Echo
2023/10/23
5140
Go 函数的健壮性、panic异常处理、defer 机制
go语言defer panic recover用法总结
函数返回的过程是这样的:先给返回值赋值,然后再调用defer表达式,最后才是返回到调用函数中
charlieroro
2020/03/24
6560
Go语言学习16-特殊流程控制(defer,error,panic,recover)
上一篇博文介绍了 Go 语言的《基本流程控制》,本篇我们介绍 Go 语言的特殊流程控制。
huazie
2024/12/25
1690
Go语言学习16-特殊流程控制(defer,error,panic,recover)
Defer,Panic,and Recover
Go拥有一般的控制流程机制,像if、for、switch、goto。除此之外go也拥有一个单独的goroutine机制运行go语句。这里我想讨论一些不太常见的语法:defer,panic,and recover
陌无崖
2020/07/27
5030
Go-defer,panic,recover
defer 语法: defer function_name() 简单来讲,在defer所在函数执行完所有的代码之后,会自动执行defer的这个函数。 示例一(基本功能) package main import "fmt" /* D:\examples>go run helloworld.go first second D:\examples> */ func main() { defer second() first() } func first() { fmt.Printl
李海彬
2018/03/23
7650
golang 详解defer
defer用来声明一个延迟函数,把这个函数放入到一个栈上, 当外部的包含方法return之前,返回参数到调用方法之前调用,也可以说是运行到最外层方法体的"}"时调用。我们经常用他来做一些资源的释放,比如关闭io操作
lpxxn
2018/03/14
8870
浅析golang中的defer
延迟执行可以用在很多的场景,比如连接数据库、打开文件、获取http连接等资源后,都需要释放资源,但是写代码的人容易忘记关闭资源的连接,且容易造成代码冗余。所以可以用defer语句在资源打开后马上调用defer去释放资源,可以避免忘记释放资源。因此,在诸如打开连接/关闭连接;申请/释放锁;打开文件/关闭文件等成对出现的操作场景里,defer会显得格外方便,如下:
素履coder
2022/02/17
5020
Go 专栏|错误处理:defer,panic 和 recover
最近校招又开始了,我也接到了一些面试工作,当我问「你觉得自己有什么优势」时,十个人里有八个的回答里会有一条「精力充沛,能加班」。
AlwaysBeta
2021/09/07
3690
Go 专栏|错误处理:defer,panic 和 recover
6.Go-错误,defer,panic和recover
  defer最常用的就是关闭连接(数据库,文件等),可以打开连接后紧跟defer进行关闭
zhang_derek
2019/08/12
4740
go 中的 defer 使用及其规则
defer的思想类似于C++中的析构函数,不过Go语言中“析构”的不是对象,而是函数,defer就是用来添加函数结束时执行的语句。
莫斯
2020/09/10
1.9K0
golang之panic,recover,defer
recover内建函数用于“拦截”运行时恐慌,可以使当前的程序从恐慌状态中恢复并重新获得流程控制权。
超蛋lhy
2018/08/31
6010
再读Golang中的异常处理 顶
注意:如果一个没有recover的goroutine发生了panic,那么整个进程都会挂掉
BGBiao
2019/10/22
7090
Golang之轻松化解defer的温柔陷阱
defer是Go语言提供的一种用于注册延迟调用的机制:让函数或语句可以在当前函数执行完毕后(包括通过return正常结束或者panic导致的异常结束)执行。
李海彬
2019/05/14
8280
Golang之轻松化解defer的温柔陷阱
defer是Go语言提供的一种用于注册延迟调用的机制:让函数或语句可以在当前函数执行完毕后(包括通过return正常结束或者panic导致的异常结束)执行。深受Go开发者的欢迎,但一不小心就会掉进它的温柔陷阱,只有深入理解它的原理,我们才能轻松避开,写出漂亮稳健的代码。
梦醒人间
2019/05/21
4040
Go 语言错误及异常处理篇(三):panic 和 recover
前面学院君介绍了 Go 语言通过 error 接口统一进行错误处理,但这些错误都是我们在编写代码时就已经预见并返回的,对于某些运行时错误,比如数组越界、除数为0、空指针引用,这些 Go 语言是怎么处理的呢?
学院君
2019/08/19
1.6K0
Go 语言错误及异常处理篇(三):panic 和 recover
golang之panic
在go语言中,panic是一种用于处理不可恢复错误和异常情况的机制。大多数情况下,我们用panic来快速解决正常运行中出现的异常情况,或者我们没有准备好优雅地处理的错误。
孤烟
2024/02/12
2131
Golang defer 快速上手
defer 用于预设一个函数调用,推迟函数的执行。被推迟的函数会在执行 defer 的函数返回之前执行。
恋喵大鲤鱼
2022/01/12
7820
探究 Go 源码中 panic & recover 有哪些坑?
写这一篇文章的原因是最近在工作中有位小伙伴在写代码的时候直接用 Go 关键字起了一个 Goroutine,然后发生了空指针的问题,由于没有 recover 导致了整个程序宕掉的问题。代码类似这样:
luozhiyun
2021/11/24
1.3K0
探究 Go 源码中 panic & recover 有哪些坑?
panic 和 recover
在 Go 语言中,程序中一般是使用错误来处理异常情况。对于程序中出现的大部分异常情况,错误就已经够用了。
酷走天涯
2019/06/11
7420
panic 和 recover
相关推荐
Golang 高效实践之defer、panic、recover实践
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档