当在同一个变量上两次调用defer时会发生什么?

huangapple go评论84阅读模式
英文:

What happens when defer is called twice on same variable?

问题

当在方法的结构发生更改时,调用defer两次会发生什么?

例如:

rows := Query(`SELECT FROM whatever`)
defer rows.Close()
for rows.Next() { 
  // do something
}
rows = Query(`SELECT FROM another`) 
defer rows.Close()
for rows.Next() {
  // do something else
}

当最后一个rows.Close()被调用时,rows是什么?

英文:

What happened when defer called twice when the struct of that method has been changed?

For example:

rows := Query(`SELECT FROM whatever`)
defer rows.Close()
for rows.Next() { 
  // do something
}
rows = Query(`SELECT FROM another`) 
defer rows.Close()
for rows.Next() {
  // do something else
}

which rows when the last rows.Close() called?

答案1

得分: 22

这取决于方法接收器和变量的类型。

简短回答:如果你使用的是database/sql包,你的延迟调用的Rows.Close()方法将正确关闭两个Rows实例,因为Rows.Close()具有指针接收器,并且DB.Query()返回一个指针(因此rows是一个指针)。请参考下面的推理和解释。

为了避免混淆,我建议使用不同的变量,这样你就可以清楚地知道你想要关闭的是什么和将要关闭的是什么:

rows := Query(`SELECT FROM whatever`)
defer rows.Close()
// ...
rows2 := Query(`SELECT FROM whatever`)
defer rows2.Close()

我想指出一个重要的事实,即延迟函数及其参数会立即被评估,这一点在Effective Go博文和语言规范:延迟语句中都有提到:

每次执行“defer”语句时,函数值和调用的参数会像往常一样被评估并重新保存,但实际的函数不会被调用。相反,延迟函数会在包围它的函数返回之前立即被调用,按照它们被延迟的相反顺序。

如果变量不是指针:当调用一个延迟方法时,根据方法是否具有指针接收器,你将观察到不同的结果。如果变量是指针,你将始终看到“期望”的结果。

看下面的例子:

type X struct {
    S string
}

func (x X) Close() {
    fmt.Println("Value-Closing", x.S)
}

func (x *X) CloseP() {
    fmt.Println("Pointer-Closing", x.S)
}

func main() {
    x := X{"Value-X First"}
    defer x.Close()
    x = X{"Value-X Second"}
    defer x.Close()

    x2 := X{"Value-X2 First"}
    defer x2.CloseP()
    x2 = X{"Value-X2 Second"}
    defer x2.CloseP()

    xp := &X{"Pointer-X First"}
    defer xp.Close()
    xp = &X{"Pointer-X Second"}
    defer xp.Close()

    xp2 := &X{"Pointer-X2 First"}
    defer xp2.CloseP()
    xp2 = &X{"Pointer-X2 Second"}
    defer xp2.CloseP()
}

输出结果:

Pointer-Closing Pointer-X2 Second
Pointer-Closing Pointer-X2 First
Value-Closing Pointer-X Second
Value-Closing Pointer-X First
Pointer-Closing Value-X2 Second
Pointer-Closing Value-X2 Second
Value-Closing Value-X Second
Value-Closing Value-X First

Go Playground上试一试。

使用指针变量,结果始终是正确的(如预期所示)。

使用非指针变量并使用指针接收器,我们看到相同的打印结果(最新的结果),但如果使用值接收器,它会打印出两个不同的结果。

非指针变量的解释:

如上所述,包括接收器在内的延迟函数在defer执行时被评估。对于指针接收器,它将是局部变量的地址。因此,当你给它赋一个新值并调用另一个defer时,指针接收器将再次是局部变量的相同地址(只是指向的值不同)。所以当函数执行时,两者都会使用相同的地址两次,但指向的值将是后面分配的那个。

对于值接收器,接收器是一个副本,在defer执行时创建,所以如果你给变量赋一个新值并调用另一个defer,将会创建另一个不同的副本。

英文:

It depends on the method receiver and on the type of the variable.

Short answer: if you're using the database/sql package, your deferred Rows.Close() methods will properly close both of your Rows instances because Rows.Close() has pointer receiver and because DB.Query() returns a pointer (and so rows is a pointer). See reasoning and explanation below.

To avoid confusion, I recommend using different variables and it will be clear what you want and what will be closed:

rows := Query(`SELECT FROM whatever`)
defer rows.Close()
// ...
rows2 := Query(`SELECT FROM whatever`)
defer rows2.Close()

I'd like to point out an important fact that comes from the deferred function and its parameters being evaluated immedately which is stated in the Effective Go blog post and in the Language Spec: Deferred statements too:

> Each time a "defer" statement executes, the function value and parameters to the call are evaluated as usual and saved anew but the actual function is not invoked. Instead, deferred functions are invoked immediately before the surrounding function returns, in the reverse order they were deferred.

If variable is not a pointer: You will observe different results when calling a method deferred, depending if the method has a pointer receiver.
If variable is a pointer, you will see always the "desired" result.

See this example:

type X struct {
	S string
}

func (x X) Close() {
	fmt.Println("Value-Closing", x.S)
}

func (x *X) CloseP() {
	fmt.Println("Pointer-Closing", x.S)
}

func main() {
	x := X{"Value-X First"}
	defer x.Close()
	x = X{"Value-X Second"}
	defer x.Close()

	x2 := X{"Value-X2 First"}
	defer x2.CloseP()
	x2 = X{"Value-X2 Second"}
	defer x2.CloseP()

	xp := &X{"Pointer-X First"}
	defer xp.Close()
	xp = &X{"Pointer-X Second"}
	defer xp.Close()

	xp2 := &X{"Pointer-X2 First"}
	defer xp2.CloseP()
	xp2 = &X{"Pointer-X2 Second"}
	defer xp2.CloseP()
}

Output:

Pointer-Closing Pointer-X2 Second
Pointer-Closing Pointer-X2 First
Value-Closing Pointer-X Second
Value-Closing Pointer-X First
Pointer-Closing Value-X2 Second
Pointer-Closing Value-X2 Second
Value-Closing Value-X Second
Value-Closing Value-X First

Try it on the Go Playground.

Using a pointer variable the result is always good (as expected).

Using a non-pointer variable and using pointer receiver we see the same printed results (the latest) but if we have value receiver, it prints 2 different results.

Explanation for non-pointer variable:

As stated, deferred function including the receiver is evaluated when the defer executes. In case of a pointer receiver it will be the address of the local variable. So when you assign a new value to it and call another defer, the pointer receiver will be again the same address of the local variable (just the pointed value is different). So later when the function is executed, both will use the same address twice but the pointed value will be the same, the one assigned later.

In case of value receiver, the receiver is a copy which is made when the defer executed, so if you assign a new value to the variable and call another defer, another copy will be made which is different from the previous one.

答案2

得分: 4

Effective Go提到:

> 延迟函数的参数(如果函数是一个方法,则包括接收器)在延迟执行时进行求值,而不是在调用执行时进行求值
>
> 除了避免担心变量在函数执行时改变值之外,这意味着单个延迟调用点可以延迟多个函数执行。

在你的情况下,延迟将引用第二行的实例。
这两个延迟函数按照LIFO顺序执行(如“Defer,Panic和Recover”中也提到的)。

正如icza他的回答评论中提到的:

> 这两个延迟的Close()方法将引用两个不同的Rows值,并且两者都将被正确关闭,因为rows是一个指针,而不是值类型。

英文:

Effective Go mentions:

> The arguments to the deferred function (which include the receiver if the function is a method) are evaluated when the defer executes, not when the call executes.
>
> Besides avoiding worries about variables changing values as the function executes, this means that a single deferred call site can defer multiple function executions

In your case, the defer would reference the second rows instance.
The two deferred functions are executed in LIFO order (as mentioned also in "Defer, Panic, and Recover").

As icza mentions in his answer and in the comments:

> The 2 deferred Close() methods will refer to the 2 distinct Rows values and both will be properly closed because rows is a pointer, not a value type.

答案3

得分: 1

啊,我明白了,rows 总是指向最后一个对象,http://play.golang.org/p/_xzxHnbFSz

package main

import "fmt"

type X struct {
   A string
}

func (x *X) Close() {
  fmt.Println(x.A)
}

func main() {
  rows := X{`1`}
  defer rows.Close()
  rows = X{`2`}
  defer rows.Close()
}

输出结果:

2
2

所以,也许保留对象的最佳方法是将其传递给一个函数:http://play.golang.org/p/TIMCliUn60

package main

import "fmt"

type X struct {
	A string
}

func (x *X) Close() {
	fmt.Println(x.A)
}

func main() {
	rows := X{`1`}
	defer func(r X) { r.Close() }(rows)
	rows = X{`2`}
	defer func(r X) { r.Close() }(rows)
}

输出结果:

2
1
英文:

Ah I see, the rows always refer to the last one, http://play.golang.org/p/_xzxHnbFSz

package main

import "fmt"

type X struct {
   A string
}

func (x *X) Close() {
  fmt.Println(x.A)
}

func main() {
  rows := X{`1`}
  defer rows.Close()
  rows = X{`2`}
  defer rows.Close()
}

Output:

2
2

So maybe the best way to preserve the object is to pass it to a function: http://play.golang.org/p/TIMCliUn60

package main

import "fmt"

type X struct {
	A string
}

func (x *X) Close() {
	fmt.Println(x.A)
}

func main() {
	rows := X{`1`}
	defer func(r X) { r.Close() }(rows)
	rows = X{`2`}
	defer func(r X) { r.Close() }(rows)
}

Output:

2
1

答案4

得分: 1

大多数情况下,你只需要添加一个代码块,这样你就不必担心想出一个新的变量名,也不必担心任何项没有被关闭:

rows := Query(`SELECT FROM whatever`)
defer rows.Close()
for rows.Next() { 
   // 做一些操作
}
{
   rows := Query(`SELECT FROM another`) 
   defer rows.Close()
   for rows.Next() {
      // 做一些其他操作
   }
}

https://golang.org/ref/spec#Blocks

英文:

Most of the time, you should be able to just add a block, that way you don't have to worry about thinking of a new variable name, and you don't have to worry about any of the items not being closed:

rows := Query(`SELECT FROM whatever`)
defer rows.Close()
for rows.Next() { 
   // do something
}
{
   rows := Query(`SELECT FROM another`) 
   defer rows.Close()
   for rows.Next() {
      // do something else
   }
}

https://golang.org/ref/spec#Blocks

huangapple
  • 本文由 发表于 2015年3月6日 14:46:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/28893586.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定