当我在函数中添加延迟时,我希望它在函数结束时始终被调用。 我注意到当函数超时时它不会发生。
package main
import (
"context"
"fmt"
"time"
)
func service1(ctx context.Context, r *Registry) {
ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
defer func() {
r.Unset("service 1")
}()
r.Set("service 1")
go service2(ctx, r)
select {
case <-ctx.Done():
cancel()
break
}
}
func service2(ctx context.Context, r *Registry) {
defer func() {
r.Unset("service 2")
}()
r.Set("service 2")
time.Sleep(time.Millisecond * 300)
}
type Registry struct {
entries map[string]bool
}
func (r *Registry)Set(key string) {
r.entries[key] = true
}
func (r *Registry)Unset(key string) {
r.entries[key] = false
}
func (r *Registry)Print() {
for key, val := range r.entries {
fmt.Printf("%s -> %v\n", key, val)
}
}
func NewRegistry() *Registry {
r := Registry{}
r.entries = make(map[string]bool)
return &r
}
func main() {
r := NewRegistry()
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*200)
go service1(ctx, r)
// go service3(ctx, r)
select {
case <-ctx.Done():
fmt.Printf("context err: %s\n", ctx.Err())
cancel()
}
r.Print()
}
在上面的示例中,service2()
中的延迟从未被调用,这就是输出的原因:
service 1 -> false
service 2 -> true
代替
service 1 -> false
service 2 -> false
我知道超时意味着“停止执行”,但对我来说执行延迟代码是合理的。我找不到对此行为的任何解释。
问题的第二部分 - 如何修改服务或 Registry
以抵抗这种情况?
最佳答案
第一部分的答案
假设您有一个函数 f1()
,它使用 defer
来调用 f2()
,即 defer f2()
。事实上,即使发生运行时 panic ,当且仅当 f1
完成时,才会调用 f2
。更具体地说,看go-defer .
现在我们关心的是在 goroutine 中使用 defer。我们还必须记住,如果 go-routine 的父函数完成退出,它就会退出。
因此,如果我们在 go-routine 函数中使用 defer
,那么如果父函数完成或退出,则 go-routine 函数必须退出。由于它退出(未完成),defer
语句将不会执行。很明显,我们绘制了您程序的状态。
如您所见,
- 在第 1 毫秒,
service1()
先于其他服务完成。因此,service2()
会在不执行defer
语句的情况下退出,并且“service 2”不会被设置为false
。由于service1()
完成,它的defer
将执行并且“服务 1”将设置为false
。 - 在第 2 毫秒,
main()
完成,程序结束。
所以我们看看这个程序是如何执行的。
第二部分的答案
我尝试过的一个可能的解决方案是增加 service1()
的时间或减少 service2()
的时间。
关于go - 为什么当我超时函数时不调用延迟?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53166848/