golang 延迟_了解Go中的延迟

news/2024/7/7 14:31:22

golang 延迟

介绍 (Introduction)

Go has many of the common control-flow keywords found in other programming languages such as if, switch, for, etc. One keyword that isn’t found in most other programming languages is defer, and though it’s less common you’ll quickly see how useful it can be in your programs.

Go有许多共同的控制流的关键字在其他编程语言,如发现ifswitchfor等,未在其他大多数编程语言中的一个关键词是defer ,虽然这是不常见的,你会很快看到它在您的程序中有多有用。

One of the primary uses of a defer statement is for cleaning up resources, such as open files, network connections, and database handles. When your program is finished with these resources, it’s important to close them to avoid exhausting the program’s limits and to allow other programs access to those resources. defer makes our code cleaner and less error prone by keeping the calls to close the file/resource in proximity to the open call.

defer语句的主要用途之一是清理资源,例如打开的文件,网络连接和数据库句柄 。 当您的程序用完这些资源后,请务必关闭它们,以免耗尽程序的限制,并允许其他程序访问这些资源。 defer通过使关闭文件/资源​​的调用保持在打开调用附近,从而使我们的代码更整洁,并且更少出错。

In this article we will learn how to properly use the defer statement for cleaning up resources as well as several common mistakes that are made when using defer.

在本文中,我们将学习如何正确使用defer语句来清理资源以及使用defer时会犯的一些常见错误。

什么是defer声明 (What is a defer Statement)

A defer statement adds the function call following the defer keyword onto a stack. All of the calls on that stack are called when the function in which they were added returns. Because the calls are placed on a stack, they are called in last-in-first-out order.

defer语句将defer关键字后面的函数调用添加到堆栈中。 当添加了它们的函数返回时,将调用该堆栈上的所有调用。 由于这些调用位于堆栈上,因此将按照后进先出的顺序进行调用。

Let’s look at how defer works by printing out some text:

让我们通过打印一些文本来查看defer工作方式:

main.go
main.go
package main

import "fmt"

func main() {
    defer fmt.Println("Bye")
    fmt.Println("Hi")
}

In the main function, we have two statements. The first statement starts with the defer keyword, followed by a print statement that prints out Bye. The next line prints out Hi.

main函数中,我们有两个语句。 第一条语句以defer关键字开头,其后是一条print语句,该语句打印出Bye 。 下一行输出Hi

If we run the program, we will see the following output:

如果运行程序,将看到以下输出:


   
Output
Hi Bye

Notice that Hi was printed first. This is because any statement that is preceded by the defer keyword isn’t invoked until the end of the function in which defer was used.

请注意, Hi首先打印。 这是因为通过之前的任何声明defer关键字,则不会调用,直到该函数的最后defer使用。

Let’s take another look at the program, and this time we’ll add some comments to help illustrate what is happening:

让我们再看一下该程序,这次我们将添加一些注释以帮助说明正在发生的事情:

main.go
main.go
package main

import "fmt"

func main() {
    // defer statement is executed, and places
    // fmt.Println("Bye") on a list to be executed prior to the function returning
    defer fmt.Println("Bye")

    // The next line is executed immediately
    fmt.Println("Hi")

    // fmt.Println*("Bye") is now invoked, as we are at the end of the function scope
}

The key to understanding defer is that when the defer statement is executed, the arguments to the deferred function are evaluated immediately. When a defer executes, it places the statement following it on a list to be invoked prior to the function returning.

理解defer的关键是,当执行defer语句时,将立即评估deferred函数的参数。 当执行defer ,它将其后的语句放在要返回的函数之前要调用的列表上。

Although this code illustrates the order in which defer would be run, it’s not a typical way it would be used when writing a Go program. It’s more likely we are using defer to clean up a resource, such as a file handle. Let’s look at how to do that next.

尽管此代码说明了执行defer的顺序,但这并不是编写Go程序时使用的典型方式。 我们更有可能使用defer清理资源,例如文件句柄。 接下来让我们看看如何做。

使用defer来清理资源 (Using defer to Clean Up Resources)

Using defer to clean up resources is very common in Go. Let’s first look at a program that writes a string to a file but does not use defer to handle the resource clean-up:

在Go中,使用defer清理资源非常普遍。 首先,让我们看一个将字符串写入文件但不使用defer进行资源清理的程序:

main.go
main.go
package main

import (
    "io"
    "log"
    "os"
)

func main() {
    if err := write("readme.txt", "This is a readme file"); err != nil {
        log.Fatal("failed to write file:", err)
    }
}

func write(fileName string, text string) error {
    file, err := os.Create(fileName)
    if err != nil {
        return err
    }
    _, err = io.WriteString(file, text)
    if err != nil {
        return err
    }
    file.Close()
    return nil
}

In this program, there is a function called write that will first attempt to create a file. If it has an error, it will return the error and exit the function. Next, it tries to write the string This is a readme file to the specified file. If it receives an error, it will return the error and exit the function. Then, the function will try to close the file and release the resource back to the system. Finally the function returns nil to signify that the function executed without error.

在此程序中,有一个名为write的函数,该函数将首先尝试创建文件。 如果有错误,它将返回错误并退出功能。 接下来,它尝试将字符串“ This is a readme file写入指定的文件。 如果收到错误,它将返回错误并退出功能。 然后,该函数将尝试关闭文件并将资源释放回系统。 最后,该函数返回nil ,表示该函数已执行且没有错误。

Although this code works, there is a subtle bug. If the call to io.WriteString fails, the function will return without closing the file and releasing the resource back to the system.

尽管此代码有效,但存在一个细微的错误。 如果对io.WriteString的调用失败,该函数将返回而不关闭文件并将资源释放回系统。

We could fix the problem by adding another file.Close() statement, which is how you would likely solve this in a language without defer:

我们可以通过添加另一个file.Close()语句来解决此问题,这是您可能会用不defer的语言解决此问题的方法:

main.go
main.go
package main

import (
    "io"
    "log"
    "os"
)

func main() {
    if err := write("readme.txt", "This is a readme file"); err != nil {
        log.Fatal("failed to write file:", err)
    }
}

func write(fileName string, text string) error {
    file, err := os.Create(fileName)
    if err != nil {
        return err
    }
    _, err = io.WriteString(file, text)
    if err != nil {
        file.Close()
        return err
    }
    file.Close()
    return nil
}

Now even if the call to io.WriteString fails, we will still close the file. While this was a relatively easy bug to spot and fix, with a more complicated function, it may have been missed.

现在,即使对io.WriteString的调用失败,我们仍将关闭文件。 尽管这是一个相对容易发现和修复的错误,但功能更复杂,但可能会错过它。

Instead of adding the second call to file.Close(), we can use a defer statement to ensure that regardless of which branches are taken during execution, we always call Close().

不必添加对file.Close()的第二次调用,我们可以使用defer语句来确保无论执行过程中采用了哪个分支,我们始终调用Close()

Here’s the version that uses the defer keyword:

这是使用defer关键字的版本:

main.go
main.go
package main

import (
    "io"
    "log"
    "os"
)

func main() {
    if err := write("readme.txt", "This is a readme file"); err != nil {
        log.Fatal("failed to write file:", err)
    }
}

func write(fileName string, text string) error {
    file, err := os.Create(fileName)
    if err != nil {
        return err
    }
    defer file.Close()
    _, err = io.WriteString(file, text)
    if err != nil {
        return err
    }
    return nil
}

This time we added the line of code: defer file.Close(). This tells the compiler that it should execute the file.Close prior to exiting the function write.

这次,我们添加了以下代码行: defer file.Close() 。 这告诉编译器它应该执行file.Close然后退出函数write

We have now ensured that even if we add more code and create another branch that exits the function in the future, we will always clean up and close the file.

现在,我们确保即使添加更多代码并创建另一个分支,以后退出该函数,我们也将始终清理并关闭文件。

However, we have introduced yet another bug by adding the defer. We are no longer checking the potential error that can be returned from the Close method. This is because when we use defer, there is no way to communicate any return value back to our function.

但是,我们通过添加defer引入了另一个错误。 我们不再检查Close方法可能返回的潜在错误。 这是因为当我们使用defer ,无法将任何返回值传递回我们的函数。

In Go, it is considered a safe and accepted practice to call Close() more than once without affecting the behavior of your program. If Close() is going to return an error, it will do so the first time it is called. This allows us to call it explicitly in the successful path of execution in our function.

在Go中,多次调用Close()被视为一种安全且可以接受的做法,同时又不影响程序的行为。 如果Close()将返回错误,则它将在第一次调用时这样做。 这使我们可以在函数的成功执行路径中显式调用它。

Let’s look at how we can both defer the call to Close, and still report on an error if we encounter one.

让我们看看如何既可以deferClose的调用,又可以在遇到错误时仍报告错误。

main.go
main.go
package main

import (
    "io"
    "log"
    "os"
)

func main() {
    if err := write("readme.txt", "This is a readme file"); err != nil {
        log.Fatal("failed to write file:", err)
    }
}

func write(fileName string, text string) error {
    file, err := os.Create(fileName)
    if err != nil {
        return err
    }
    defer file.Close()
    _, err = io.WriteString(file, text)
    if err != nil {
        return err
    }

    return file.Close()
}

The only change in this program is the last line in which we return file.Close(). If the call to Close results in an error, this will now be returned as expected to the calling function. Keep in mind that our defer file.Close() statement is also going to run after the return statement. This means that file.Close() is potentially called twice. While this isn’t ideal, it is an acceptable practice as it should not create any side effects to your program.

该程序的唯一变化是我们返回file.Close()的最后一行。 如果对Close的调用导致错误,则现在将按预期将其返回给调用函数。 请记住,我们的defer file.Close()语句也将在return语句之后运行。 这意味着file.Close()可能被调用两次。 虽然这不是理想的方法,但是它是可以接受的做法,因为它不会对程序产生任何副作用。

If, however, we receive an error earlier in the function, such as when we call WriteString, the function will return that error, and will also try to call file.Close because it was deferred. Although file.Close may (and likely will) return an error as well, it is no longer something we care about as we received an error that is more likely to tell us what went wrong to begin with.

但是,如果在函数中更早收到错误(例如,当我们调用WriteString ,该函数将返回该错误,并且还将尝试调用file.Close因为它已被延迟。 尽管file.Close也可能(并且很可能会)返回错误,但是我们不再关心它,因为我们收到的错误更有可能告诉我们开始出错了。

So far, we have seen how we can use a single defer to ensure that we clean up our resources properly. Next we will see how we can use multiple defer statements for cleaning up more than one resource.

到目前为止,我们已经看到了如何使用单个defer来确保我们正确清理资源。 接下来,我们将看到如何使用多个defer语句来清理多个资源。

多个defer声明 (Multiple defer Statements)

It is normal to have more than one defer statement in a function. Let’s create a program that only has defer statements in it to see what happens when we introduce multiple defers:

一个函数中有多个defer语句是正常的。 让我们创建一个仅包含defer语句的程序,看看引入多个defer时会发生什么:

main.go
main.go
package main

import "fmt"

func main() {
    defer fmt.Println("one")
    defer fmt.Println("two")
    defer fmt.Println("three")
}

If we run the program, we will receive the following output:

如果运行程序,将收到以下输出:


   
Output
three two one

Notice that the order is the opposite in which we called the defer statements. This is because each deferred statement that is called is stacked on top of the previous one, and then called in reverse when the function exits scope (Last In, First Out).

注意,顺序与我们称为defer语句的顺序相反。 这是因为每个被调用的延迟语句都堆叠在前一个语句的顶部,然后在函数退出作用域( Last In,First Out )时反向调用。

You can have as many deferred calls as needed in a function, but it is important to remember they will all be called in the opposite order they were executed.

您可以根据需要在函数中进行任意数量的延迟调用,但是要记住,所有调用都将以与执行相反的顺序进行调用。

Now that we understand the order in which multiple defers will execute, let’s see how we would use multiple defers to clean up multiple resources. We’ll create a program that opens a file, writes to it, then opens it again to copy the contents to another file.

现在我们了解了多个延迟执行的顺序,让我们看看如何使用多个延迟来清理多个资源。 我们将创建一个程序,该程序打开一个文件,对其进行写入,然后再次打开以将内容复制到另一个文件。

main.go
main.go
package main

import (
    "fmt"
    "io"
    "log"
    "os"
)

func main() {
    if err := write("sample.txt", "This file contains some sample text."); err != nil {
        log.Fatal("failed to create file")
    }

    if err := fileCopy("sample.txt", "sample-copy.txt"); err != nil {
        log.Fatal("failed to copy file: %s")
    }
}

func write(fileName string, text string) error {
    file, err := os.Create(fileName)
    if err != nil {
        return err
    }
    defer file.Close()
    _, err = io.WriteString(file, text)
    if err != nil {
        return err
    }

    return file.Close()
}

func fileCopy(source string, destination string) error {
    src, err := os.Open(source)
    if err != nil {
        return err
    }
    defer src.Close()

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

    n, err := io.Copy(dst, src)
    if err != nil {
        return err
    }
    fmt.Printf("Copied %d bytes from %s to %s\n", n, source, destination)

    if err := src.Close(); err != nil {
        return err
    }

    return dst.Close()
}

We added a new function called fileCopy. In this function, we first open up our source file that we are going to copy from. We check to see if we received an error opening the file. If so, we return the error and exit the function. Otherwise, we defer the closing of the source file we just opened.

我们添加了一个名为fileCopy的新功能。 在此功能中,我们首先打开要复制的源文件。 我们检查是否打开文件时收到错误。 如果是这样,我们将return错误并退出该函数。 否则,我们将defer刚刚打开的源文件的关闭。

Next we create the destination file. Again, we check to see if we received an error creating the file. If so, we return that error and exit the function. Otherwise, we also defer the Close() for the destination file. We now have two defer functions that will be called when the function exits its scope.

接下来,我们创建目标文件。 再次,我们检查是否在创建文件时收到错误。 如果是这样,我们将return该错误并退出该函数。 否则,我们还将defer目标文件的Close() 。 现在,我们有两个defer函数,当它们退出作用域时将被调用。

Now that we have both files open, we will Copy() the data from the source file to the destination file. If that is successful, we will attempt to close both files. If we receive an error trying to close either file, we will return the error and exit function scope.

现在我们都打开了两个文件,我们将把数据从源文件Copy()到目标文件。 如果成功,我们将尝试关闭两个文件。 如果在尝试关闭两个文件时收到错误,我们将return错误并退出函数范围。

Notice that we explicitly call Close() for each file, even though the defer will also call Close(). This is to ensure that if there is an error closing a file, we report the error. It also ensures that if for any reason the function exits early with an error, for instance if we failed to copy between the two files, that each file will still try to close properly from the deferred calls.

请注意,我们显式调用Close()为每个文件,即使defer也将调用Close() 。 这是为了确保关闭文件时出现错误,我们会报告该错误。 它还可以确保,如果由于某种原因该函数因错误而提前退出,例如,如果我们未能在两个文件之间进行复制,则每个文件仍将尝试从延迟的调用中正确关闭。

结论 (Conclusion)

In this article we learned about the defer statement, and how it can be used to ensure that we properly clean up system resources in our program. Properly cleaning up system resources will make your program use less memory and perform better. To learn more about where defer is used, read the article on Handling Panics, or explore our entire How To Code in Go series.

在本文中,我们了解了defer语句,以及如何使用它来确保我们正确清理程序中的系统资源。 正确清理系统资源将使您的程序使用更少的内存,并获得更好的性能。 要了解有关在何处使用defer更多信息,请阅读有关处理恐慌的文章,或浏览我们的《 如何编写代码》系列 。

翻译自: https://www.digitalocean.com/community/tutorials/understanding-defer-in-go

golang 延迟


http://www.niftyadmin.cn/n/3649760.html

相关文章

如何安装svelte_在Svelte中使用if块

如何安装svelteIn this tutorial, we’ll see how to use the if blocks with the Svelte framework to add some flow control logic to our Svelte markup code. 在本教程中,我们将看到如何在Svelte框架中使用if块向Svelte标记代码添加一些流控制逻辑。 Blocks i…

[EntLibFAQ]“不允许所请求的注册表访问权”的解释[0508Update]

[EntLibFAQ]“不允许所请求的注册表访问权”的解释VersionDateCreatorDescription1.0.0.12006-5-2郑昀Ultrapower草稿继续阅读之前,我们假设您熟悉以下知识:n Microsoft Enterprise Library June 2005n EventLog和注册表的关系[现象]首先…

mac node repl_如何使用Node.js REPL

mac node replThe author selected the Open Internet/Free Speech Fund to receive a donation as part of the Write for DOnations program. 作者选择了“ 开放互联网/言论自由基金会”作为“ Write for DOnations”计划的一部分来接受捐赠。 介绍 (Introduction) The Node…

[Remoting FAQ]传递Remoting参数时遇到的两种常见错误

[Remoting FAQ]传递Remoting参数时遇到的两种常见错误VersionDateCreatorDescription1.0.0.12006-4-25郑昀Ultrapower草稿继续阅读之前,我们假设您熟悉以下知识:n Remoting[现象1]我们先来描述一个简单的错误。当你激活远端Remoting Objects时&a…

greensock下载_使用GreenSock绘制Alligator.io SVG徽标

greensock下载To get the most out of this article it is important that you have a solid understanding of JavaScript. We will be solely focusing on understanding GreenSock in this article, so if you haven’t used JavaScript before then get learning and come …

[收藏]不仅仅跟随更能够提出震撼性的技术框架/技术特点出来

“很久以前Michael Chen写过一篇blog,表达了Michael Chen对目前国内技术现状的一种思考:希望有更多的人能够不仅仅跟随,更能够提出震撼性的技术框架/技术特点出来。然而,那个时候许多人包括干脆放弃了希望安心的做起了传教士&…

[J2ME QA]MMAPICannot parse this type of AMR异常之讨论

[J2ME] MMAPI的Cannot parse this type of AMR异常之讨论郑昀 草拟 20060417[现象]首先,我们假设遇到这种错误的人们了解如何使用MMAPI,从而排除代码使用不当问题。那么在播放3gp媒体文件时遇到“java.lang.Exception: Cannot parse this type of AMR”的…

flutter布局中的单位_在Flutter中创建基本布局

flutter布局中的单位As Flutter quickly becomes one of the leading technologies in app development, it is important for even someone in the web dev environment to check out. Coming from a background of HTML, CSS and JavaScript, the way Flutter handled everyt…