【发布时间】:2018-11-06 06:39:05
【问题描述】:
当我在函数中添加一个 defer 时,我希望它总是在函数结束时被调用。 我注意到当函数超时时它不会发生。
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() 中的 defer 从未被调用,这就是输出为:
service 1 -> false
service 2 -> true
而不是
service 1 -> false
service 2 -> false
我知道超时意味着“停止执行”,但执行延迟代码对我来说是合理的。我找不到对这种行为的任何解释。
以及问题的第二部分——如何修改服务或Registry 来抵抗这种情况?
【问题讨论】:
-
可能是竞争条件?您的 r 注册表变量没有锁定?
-
main函数在service2返回之前调用r.Print()。地图上有数据竞赛(使用go -race main.go运行程序以查看竞赛)。 -
你的程序可以打印任何东西,例如甚至是
service 1 -> hubbu bubba trallala,因为它是畸形的,因为它是活泼的。 -
“从不调用 service2() 中的延迟”。是的。 service2 开始运行后大约 300 毫秒。 “我明白超时意味着‘停止执行’”。不,它没有,至少没有关于上下文包。取消上下文并没有做任何花哨的事情。它只是关闭了一个频道。
-
请在问题中输入您的代码。 Playgrounds 链接很棒,但不应要求您理解您的问题 - 特别是考虑到外部链接最终往往会过时。
标签: go concurrency