英文:
Defer function execution order
问题
我正在学习golang源代码,并在defer函数的执行顺序中遇到了困难。
我有两个文件:一个定义了端点的行为,另一个用于测试。为了减少需要阅读的行数,我删除了与问题无关的一些代码。
端点定义文件如下:
// Endpoint是服务器和客户端的基本构建块。
// 它表示一个RPC方法。
type Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error)
// Middleware是端点的可链式行为修改器。
type Middleware func(Endpoint) Endpoint
// Chain是用于组合中间件的辅助函数。请求将按照声明的顺序遍历它们。也就是说,第一个中间件被视为最外层的中间件。
func Chain(outer Middleware, others ...Middleware) Middleware {
return func(next Endpoint) Endpoint {
for i := len(others) - 1; i >= 0; i-- { // reverse
next = others[i](next)
}
return outer(next)
}
}
测试文件包含打印的步骤。
func ExampleChain() {
e := endpoint.Chain(
annotate("first"),
annotate("second"),
annotate("third"),
)(myEndpoint)
if _, err := e(ctx, req); err != nil {
panic(err)
}
// Output:
// first pre
// second pre
// third pre
// my endpoint!
// third post
// second post
// first post
}
var (
ctx = context.Background()
req = struct{}{}
)
func annotate(s string) endpoint.Middleware {
return func(next endpoint.Endpoint) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
fmt.Println(s, "pre")
defer fmt.Println(s, "post")
return next(ctx, request)
}
}
}
func myEndpoint(context.Context, interface{}) (interface{}, error) {
fmt.Println("my endpoint!")
return struct{}{}, nil
}
根据我的理解,应该首先执行三个annotate方法,然后执行endpoint.Chain方法,最后执行myEndpoint。而且由于pre先打印,当函数返回时应该紧随其后打印post,根据Go文档中对defer的解释:
“defer”语句会调用一个函数,该函数的执行被推迟到包围函数返回的时刻,无论是因为包围函数执行了return语句,到达了函数体的结尾,还是因为相应的goroutine正在panic。
所以我期望看到的输出是:
// Output:
// first pre
// first post
// second pre
// second post
// third pre
// third post
// my endpoint!
简而言之,我的问题是:
- 为什么
first pre后面没有跟着first post,同样的情况也发生在second和third上。 post的顺序被颠倒了。endpoint.Chain反转了一系列annotate返回值的执行顺序,但是annotate方法首先被评估,对吗?更不用说,pre被打印出来,这意味着内部函数首先被执行。
英文:
I am learning the golang source code and get stuck in the defer function execution order.
I have two files: one defines the behavior of an endpoint and another one for the test. I remove some code unrelated to my question to reduce the lines to read.
The endpoint definition file
// Endpoint is the fundamental building block of servers and clients.
// It represents a single RPC method.
type Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error)
// Middleware is a chainable behavior modifier for endpoints.
type Middleware func(Endpoint) Endpoint
// Chain is a helper function for composing middlewares. Requests will
// traverse them in the order they're declared. That is, the first middleware
// is treated as the outermost middleware.
func Chain(outer Middleware, others ...Middleware) Middleware {
return func(next Endpoint) Endpoint {
for i := len(others) - 1; i >= 0; i-- { // reverse
next = others[i](next)
}
return outer(next)
}
}
The test file contains the printed steps.
func ExampleChain() {
e := endpoint.Chain(
annotate("first"),
annotate("second"),
annotate("third"),
)(myEndpoint)
if _, err := e(ctx, req); err != nil {
panic(err)
}
// Output:
// first pre
// second pre
// third pre
// my endpoint!
// third post
// second post
// first post
}
var (
ctx = context.Background()
req = struct{}{}
)
func annotate(s string) endpoint.Middleware {
return func(next endpoint.Endpoint) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
fmt.Println(s, "pre")
defer fmt.Println(s, "post")
return next(ctx, request)
}
}
}
func myEndpoint(context.Context, interface{}) (interface{}, error) {
fmt.Println("my endpoint!")
return struct{}{}, nil
}
To my understanding, the three annotate methods should be executed first, followed by the endpoint.Chain method and myEndpoint should be executed in the end. Also since the pre is printed first and when the funtion returns "post" should follow according to the defer explanation in the go doc:
A "defer" statement invokes a function whose execution is deferred to the moment the surrounding function returns, either because the surrounding function executed a return statement, reached the end of its function body, or because the corresponding goroutine is panicking.
So what I expect to see is
// Output:
// first pre
// first post
// second pre
// second post
// third pre
// third post
// my endpoint!
In short, my questions are:
- why
first preis not followed byfirst post, same assecondthird. - the order of
posts is reversed. theendpoint.Chainreverse the execution of a list ofannotatereturned values butannotatemethods are evaluated first right? Not to say, thepres get printed which means the inner funtions are executed first
答案1
得分: 2
一个延迟函数在函数的最后一件事情之后运行,即在返回语句之后,所以annotate函数将首先运行next,只有在next返回之后延迟函数才会运行。根据你的代码,它应该按照以下顺序打印:
first pre
second pre
third pre
my endpoint
third post
second post
first post
英文:
A deferred function runs as the last thing in the function, after the return statement, so the annotate function will first run next, and only after that returns the deferred function will run. Based on your code, the order it should print is:
first pre
second pre
third pre
my endpoint
third post
second post
first post
答案2
得分: 2
以下是你的示例代码转换为在Go Playground上运行的代码:
请注意,如果在给定的函数中调用defer多次,每个延迟调用都按照LIFO(后进先出)的顺序执行。因此,如果你想使用defer确保post先被调用,然后再执行next,可以考虑将以下代码替换为:
defer fmt.Println(s, "post")
next(ctx, request)
改为:
defer next(ctx, request)
defer fmt.Println(s, "post")
当然,在你的情况下,你想要返回next的返回值,这会带来一个小问题。为了解决这个问题,你需要一个小函数和一些命名返回值:
defer func() { i, e = next(ctx, request) }()
其中i和e是命名返回值。
这是相同的代码转换为一个新的示例,在该示例中延迟调用按照所需的顺序发生。 在这种情况下,示例有些愚蠢,因为没有发生恐慌,也没有在两个fmt.Println调用之间发生“危险步骤”,所以我们只需要按顺序执行这两个调用,而不使用defer。但是,如果我们可以在fmt.Println(s, "pre")和post部分之间发生恐慌,那么使用defer可能是有意义的。
英文:
Here is your example turned into something that runs on the Go playground.
Note that if you call defer more than once in a given function, each deferred call runs in LIFO order. So if you want to use defer to make sure your post gets called first, and then the next operates, consider replacing:
defer fmt.Println(s, "post")
next(ctx, request)
with:
defer next(ctx, request)
defer fmt.Println(s, "post)
Of course, in your case you want to return what next returns, which creates a small problem. To work around this in real cases you need a small function and some named return values:
defer func() { i, e = next(ctx, request) }()
where i and e are the named return values.
Here is that same code turned into a new example in which the deferred calls occur in the desired order. In this case, the example is rather silly, as nothing panics and there are no "dangerous steps" in between, so all we really need is to do the two fmt.Println calls sequentially, without using defer. But if we could panic between the fmt.Println(s, "pre") and the post section, then this might make sense.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论