golang中的defer在循环中是如何工作的?

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

How does golang defer work in cycles?

问题

我正在尝试处理与MongoDB的重新连接。为此,我尝试执行每个操作三次(以防它因io.EOF而失败)。

type MongoDB struct {
	session *mgo.Session
	DB      *mgo.Database
}

func (d MongoDB) performWithReconnect(collection string, 
operation func(*mgo.Collection) error) error {
	var err error
	for i := 0; i < 3; i++ {
		session := d.session.Copy()
		defer session.Close()
		err = operation(session.DB(Config.MongoDb).C(collection))
		if err == io.EOF {
			continue
		}
		if err == nil {
			return err
		}
	}
	return err
}

所以问题是关于defer语句。它会像我想的那样关闭所有会话吗,还是会有其他行为?如果你知道一些处理这种情况的好方法,我会很高兴阅读它们。

英文:

I'm trying to handle reconnections to MongoDB. To do this I try to perform every operation three times (in case it fails with io.EOF)

type MongoDB struct {
  	session *mgo.Session
    DB      *mgo.Database
}

func (d MongoDB) performWithReconnect(collection string, 
operation func(*mgo.Collection) error) error {
	var err error
	for i := 0; i &lt; 3; i++ {
	    session := d.session.Copy()
	    defer session.Close()
	    err = operation(session.DB(Config.MongoDb).C(collection))
	    if err == io.EOF{
		    continue
	    }
	    if err == nil{
		    return err
	    }
    }
	return err
}

So the question is about defer. Will it close all sessions as I suppose or it is going to behave some other way?
If you know some good practices to handle this different way I will be happy to read them.

答案1

得分: 6

考虑以下程序:

package main

import (
	"fmt"
)

func print(s string, i int) {
	fmt.Println(s, i)
}

func main() {

	for i := 0; i < 3; i++ {
		defer print("loop", i)
	}

	fmt.Println("after loop 1")

	for i := 0; i < 3; i++ {
		func(i int) {
			defer print("func", i)
		}(i)
	}

	fmt.Println("after loop 2")

}

它将打印:

after loop 1
func 0
func 1
func 2
after loop 2
loop 2
loop 1
loop 0

延迟函数调用将被放入堆栈中,并在包围函数结束时按相反的顺序执行。在您的情况下,这将是相当糟糕的,因为您将有等待关闭的连接。

我建议将循环的内容包装在内联函数中。它将按您希望的方式调用延迟函数。

英文:

Consider the following program

package main

import (
	&quot;fmt&quot;
)

func print(s string, i int) {
	fmt.Println(s, i)
}

func main() {

	for i := 0; i &lt; 3; i++ {
		defer print(&quot;loop&quot;, i)
	}

	fmt.Println(&quot;after loop 1&quot;)

	for i := 0; i &lt; 3; i++ {
		func(i int) {
			defer print(&quot;func&quot;, i)
		}(i)
	}

	fmt.Println(&quot;after loop 2&quot;)

}

It will print

after loop 1
func 0
func 1
func 2
after loop 2
loop 2
loop 1
loop 0

The deferred function calls will be put on stack and then executed in a reverse order at the end of surrounding function. In your case it will be quite bad as you will have connections waiting to be closed.

I recommend wrapping the contents of loop into an inline function. It will call deferred function just as you want it.

答案2

得分: 2

在你的代码中,你创建了三个(相同的)defer函数,它们将在函数退出时运行。

如果你需要在循环内部运行defer,你必须将其放在一个函数内部。可以通过匿名函数来实现:

for i := 0; i < 3; i++ {
    err := func() error {
        session := d.session.Copy()
        defer session.Close()
        return operation(session.DB(Config.MongoDb).C(collection))
    }()
    if err == io.EOF {
        continue
    }
    if err != nil {
        return err
    }
}

这样,每次循环时都会创建一个新的函数,并在函数内部使用defer语句。

英文:

From A Tour of Go:

> A defer statement defers the execution of a function until the surrounding function returns.

So in your code, you're creating three (identical) defer functions, which will all run when the function exits.

If you need a defer to run inside of a loop, you have to put it inside of a function. This can be done in an anonymous function thusly:

for i := 0; i &lt; 3; i++ {
    err := func() error {
        session := d.session.Copy()
        defer session.Close()
        return operation(session.DB(Config.MongoDb).C(collection))
    }()
    if err == io.EOF {
        continue
    }
    if err != nil {
        return err
    }
}

huangapple
  • 本文由 发表于 2017年4月12日 04:35:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/43355748.html
匿名

发表评论

匿名网友

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

确定