Golang推迟澄清

时间:2015-03-06 06:46:02

标签: go

当该方法的结构被更改时,当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最后rows.Close()被调用?

4 个答案:

答案 0 :(得分:16)

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

简短回答:如果您使用的是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博客文章和Language Spec: Deferred statements中也有说明:

  

每次执行“延迟”语句时,将像往常一样评估调用的函数值和参数,并重新保存但不调用实际函数。而是在周围函数返回之前立即调用延迟函数,其顺序与延迟相反。

如果变量不是指针:当调用延迟的方法时,您将观察到不同的结果,具体取决于方法是否具有指针接收器。
如果变量是指针,您将始终看到“所需”的结果。

见这个例子:

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上尝试。

使用指针变量,结果总是好的(如预期的那样)。

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

非指针变量的说明:

如上所述,当defer执行时,将评估包括接收器的延迟函数。在指针接收器的情况下,它将是局部变量的地址。因此,当您为其分配新值并调用另一个defer时,指针接收器将再次成为局部变量的相同地址(只是指向的值不同)。所以稍后当执行该函数时,两者将使用相同的地址两次,但指向的值将是相同的,稍后指定的值。

对于值接收器,接收器是副本,它是在执行defer时生成的,因此如果为变量分配新值并调用另一个defer },将制作另一个与前一个不同的副本。

答案 1 :(得分:4)

Effective Go提及:

  

延迟函数的参数(包括接收器,如果函数是方法)在延迟执行时评估,而不是在执行调用时

     

除了避免担心变量在函数执行时更改值,这意味着单个延迟调用站点可以推迟多个函数执行

在您的情况下,延迟将引用第二行实例 两个延迟函数以LIFO顺序执行(如" Defer, Panic, and Recover")中所述。

iczahis answer中提及in the comments

  

2个延迟Close()方法将引用 2个不同的行值,两者都将正确关闭,因为rows指针,不是值类型。

答案 2 :(得分:2)

啊,我看到,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

答案 3 :(得分:1)

大多数时候,你应该可以只添加一个块,这样你就没有 不用担心想一个新的变量名,而不必担心 任何未关闭的项目:

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