英文:
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 pre
is not followed byfirst post
, same assecond
third
. - the order of
post
s is reversed. theendpoint.Chain
reverse the execution of a list ofannotate
returned values butannotate
methods are evaluated first right? Not to say, thepre
s 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论