首页 后端开发 Golang Golang Defer:堆分配、堆栈分配、开放编码的 Defer

Golang Defer:堆分配、堆栈分配、开放编码的 Defer

Aug 07, 2024 pm 06:10 PM

这是帖子的摘录;完整的帖子可以在这里找到:Golang Defer:从基础到陷阱。

defer 语句可能是我们开始学习 Go 时首先发现非常有趣的事情之一,对吧?

但是它还有很多让很多人困惑的地方,并且有许多令人着迷的方面是我们在使用它时经常没有触及的。

Golang Defer: Heap-allocated, Stack-allocated, Open-coded Defer

堆分配、堆栈分配、开放编码延迟

例如,defer 语句实际上有 3 种类型(从 Go 1.22 开始,尽管以后可能会改变):开放编码 defer、堆分配 defer 和堆栈分配。每一种都有不同的性能和不同的最佳使用场景,如果您想优化性能,了解这一点很有帮助。

在本次讨论中,我们将涵盖从基础知识到更高级用法的所有内容,我们甚至会深入研究一些内部细节。

什么是延期?

在我们深入探讨之前,让我们快速浏览一下 defer。

在 Go 中,defer 是一个关键字,用于延迟函数的执行,直到周围的函数完成。

func main() {
  defer fmt.Println("hello")
  fmt.Println("world")
}

// Output:
// world
// hello
登录后复制

在此代码片段中,defer 语句安排 fmt.Println("hello") 在 main 函数的最后执行。因此,立即调用 fmt.Println("world"),并首先打印“world”。之后,因为我们使用了 defer,所以在 main 完成之前的最后一步会打印“hello”。

这就像设置一个任务稍后运行,就在函数退出之前。这对于清理操作非常有用,例如关闭数据库连接、释放互斥体或关闭文件:

func doSomething() error {
  f, err := os.Open("phuong-secrets.txt")
  if err != nil {
    return err
  }
  defer f.Close()

  // ...
}
登录后复制

上面的代码是展示 defer 如何工作的一个很好的例子,但这也是一个糟糕的使用 defer 的方式。我们将在下一节中讨论这个问题。

“好吧,很好,但是为什么不把 f.Close() 放在最后呢?”

这样做有几个很好的理由:

  • 我们将关闭操作放在打开附近,这样更容易遵循逻辑并避免忘记关闭文件。我不想向下滚动函数来检查文件是否已关闭;它分散了我对主要逻辑的注意力。
  • 即使发生恐慌(运行时错误),延迟函数也会在函数返回时被调用。

当发生恐慌时,堆栈将被展开,并且延迟函数将按特定顺序执行,我们将在下一节中介绍。

延迟已堆积

当您在函数中使用多个 defer 语句时,它们会按“堆栈”顺序执行,这意味着最后一个延迟函数首先执行。

func main() {
  defer fmt.Println(1)
  defer fmt.Println(2)
  defer fmt.Println(3)
}

// Output:
// 3
// 2
// 1
登录后复制

每次调用 defer 语句时,都会将该函数添加到当前 goroutine 链表的顶部,如下所示:

Golang Defer: Heap-allocated, Stack-allocated, Open-coded Defer

Goroutine 延迟链

当函数返回时,它会遍历链表并按照上图所示的顺序执行每个链表。

但是请记住,它不会执行 goroutine 链表中的所有 defer,它只运行返回函数中的 defer,因为我们的 defer 链表可能包含来自许多不同函数的许多 defer。

func B() {
  defer fmt.Println(1)
  defer fmt.Println(2)
  A()
}

func A() {
  defer fmt.Println(3)
  defer fmt.Println(4)
}
登录后复制

因此,仅执行当前函数(或当前堆栈帧)中的延迟函数。

Golang Defer: Heap-allocated, Stack-allocated, Open-coded Defer

Goroutine 延迟链

但是有一种典型情况,当前 goroutine 中的所有延迟函数都被跟踪并执行,那就是发生恐慌的时候。

延迟、恐慌和恢复

除了编译时错误之外,我们还有很多运行时错误:除以零(仅限整数)、越界、取消引用 nil 指针等等。这些错误会导致应用程序出现恐慌。

Panic 是一种停止当前 goroutine 执行、展开堆栈并执行当前 goroutine 中的延迟函数的方法,从而导致我们的应用程序崩溃。

为了处理意外错误并防止应用程序崩溃,您可以在延迟函数中使用恢复函数来重新获得对恐慌 goroutine 的控制。

func main() {
  defer func() {
    if r := recover(); r != nil {
      fmt.Println("Recovered:", r)
    }
  }()

  panic("This is a panic")
}

// Output:
// Recovered: This is a panic
登录后复制

通常,人们在恐慌中放入错误并使用recover(..)捕获该错误,但它可以是任何东西:字符串、整数等。

In the example above, inside the deferred function is the only place you can use recover. Let me explain this a bit more.

There are a couple of mistakes we could list here. I’ve seen at least three snippets like this in real code.

The first one is, using recover directly as a deferred function:

func main() {
  defer recover()

  panic("This is a panic")
}
登录后复制

The code above still panics, and this is by design of the Go runtime.

The recover function is meant to catch a panic, but it has to be called within a deferred function to work properly.

Behind the scenes, our call to recover is actually the runtime.gorecover, and it checks that the recover call is happening in the right context, specifically from the correct deferred function that was active when the panic occurred.

"Does that mean we can’t use recover in a function inside a deferred function, like this?"

func myRecover() {
  if r := recover(); r != nil {
    fmt.Println("Recovered:", r)
  }
}

func main() {
  defer func() {
    myRecover()
    // ...
  }()

  panic("This is a panic")
}
登录后复制

Exactly, the code above won’t work as you might expect. That’s because recover isn’t called directly from a deferred function but from a nested function.

Now, another mistake is trying to catch a panic from a different goroutine:

func main() {
  defer func() {
    if r := recover(); r != nil {
      fmt.Println("Recovered:", r)
    }
  }()

  go panic("This is a panic")

  time.Sleep(1 * time.Second) // Wait for the goroutine to finish
}
登录后复制

Makes sense, right? We already know that defer chains belong to a specific goroutine. It would be tough if one goroutine could intervene in another to handle the panic since each goroutine has its own stack.

Unfortunately, the only way out in this case is crashing the application if we don’t handle the panic in that goroutine.

Defer arguments, including receiver are immediately evaluated

I've run into this problem before, where old data got pushed to the analytics system, and it was tough to figure out why.

Here’s what I mean:

func pushAnalytic(a int) {
  fmt.Println(a)
}

func main() {
  a := 10
  defer pushAnalytic(a)

  a = 20
}
登录后复制

What do you think the output will be? It's 10, not 20.

That's because when you use the defer statement, it grabs the values right then. This is called "capture by value." So, the value of a that gets sent to pushAnalytic is set to 10 when the defer is scheduled, even though a changes later.

There are two ways to fix this.

...

Full post is available here: Golang Defer: From Basic To Trap.

以上是Golang Defer:堆分配、堆栈分配、开放编码的 Defer的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

<🎜>:泡泡胶模拟器无穷大 - 如何获取和使用皇家钥匙
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系统,解释
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆树的耳语 - 如何解锁抓钩
3 周前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

热门话题

Java教程
1672
14
CakePHP 教程
1428
52
Laravel 教程
1333
25
PHP教程
1277
29
C# 教程
1257
24
Golang vs. Python:性能和可伸缩性 Golang vs. Python:性能和可伸缩性 Apr 19, 2025 am 12:18 AM

Golang在性能和可扩展性方面优于Python。1)Golang的编译型特性和高效并发模型使其在高并发场景下表现出色。2)Python作为解释型语言,执行速度较慢,但通过工具如Cython可优化性能。

Golang和C:并发与原始速度 Golang和C:并发与原始速度 Apr 21, 2025 am 12:16 AM

Golang在并发性上优于C ,而C 在原始速度上优于Golang。1)Golang通过goroutine和channel实现高效并发,适合处理大量并发任务。2)C 通过编译器优化和标准库,提供接近硬件的高性能,适合需要极致优化的应用。

开始GO:初学者指南 开始GO:初学者指南 Apr 26, 2025 am 12:21 AM

goisidealforbeginnersandsubableforforcloudnetworkservicesduetoitssimplicity,效率和concurrencyFeatures.1)installgromtheofficialwebsitealwebsiteandverifywith'.2)

Golang vs.C:性能和速度比较 Golang vs.C:性能和速度比较 Apr 21, 2025 am 12:13 AM

Golang适合快速开发和并发场景,C 适用于需要极致性能和低级控制的场景。1)Golang通过垃圾回收和并发机制提升性能,适合高并发Web服务开发。2)C 通过手动内存管理和编译器优化达到极致性能,适用于嵌入式系统开发。

Golang vs. Python:主要差异和相似之处 Golang vs. Python:主要差异和相似之处 Apr 17, 2025 am 12:15 AM

Golang和Python各有优势:Golang适合高性能和并发编程,Python适用于数据科学和Web开发。 Golang以其并发模型和高效性能着称,Python则以简洁语法和丰富库生态系统着称。

Golang和C:性能的权衡 Golang和C:性能的权衡 Apr 17, 2025 am 12:18 AM

Golang和C 在性能上的差异主要体现在内存管理、编译优化和运行时效率等方面。1)Golang的垃圾回收机制方便但可能影响性能,2)C 的手动内存管理和编译器优化在递归计算中表现更为高效。

表演竞赛:Golang vs.C 表演竞赛:Golang vs.C Apr 16, 2025 am 12:07 AM

Golang和C 在性能竞赛中的表现各有优势:1)Golang适合高并发和快速开发,2)C 提供更高性能和细粒度控制。选择应基于项目需求和团队技术栈。

Golang vs. Python:利弊 Golang vs. Python:利弊 Apr 21, 2025 am 12:17 AM

Golangisidealforbuildingscalablesystemsduetoitsefficiencyandconcurrency,whilePythonexcelsinquickscriptinganddataanalysisduetoitssimplicityandvastecosystem.Golang'sdesignencouragesclean,readablecodeanditsgoroutinesenableefficientconcurrentoperations,t

See all articles