为什么在我使函数超时时不调用延迟?

时间:2018-11-06 06:39:05

标签: go concurrency

当我在函数中添加延迟时,我希望在函数结束时将始终调用该延迟。 我注意到当函数超时时不会发生。

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以抵抗这种情况?

1 个答案:

答案 0 :(得分:0)

第一部分的答案

假设您有一个函数f1(),该函数使用defer来调用f2(),即defer f2()。事实是,f2将在且仅当f1完成时才被调用,即使在运行时出现紧急情况时也是如此。更具体地说,请看go-defer

现在,我们担心的是在goroutine中使用defer。我们还必须记住,如果go-routine的父函数完成了退出操作,则该例程会退出。

因此,如果我们在go-routine函数中使用defer,则如果父函数完成或退出,则go-routine函数必须退出。由于退出(未完成),因此defer语句将不会执行。很明显,我们绘制了程序的状态。 enter image description here 如您所见,

  • 在第一毫秒,service1()先于其他毫秒完成。因此,service2()退出而不执行defer语句,并且“服务2”将不会设置为false。由于service1()完成,将执行defer,并将“服务1”设置为false
  • 在第二毫秒,main()完成并完成编程。

因此,我们了解了该程序的执行方式。

第二部分的答案

我尝试过的一种可能的解决方案是增加时间service1()或减少时间service2()