首页 > 解决方案 > 为什么我的程序在添加 time.sleep 时会挂起?

问题描述

我正在尝试对我的程序进行测试,该程序将在设定的时间间隔内保存到磁盘。我的问题是当我在测试中添加 time.Sleep 时,无论持续时间如何,程序都会挂起。

我的预期行为是我允许它休眠,并且 go 例程应该在后台运行并保存到磁盘的持续时间。

这是正在作为测试中的 goroutine 运行的有问题的函数。

节点服务.go

func runRegistryBackup(reg *NodeRegistry, interval int, killchan chan bool) {
log.SetPrefix("[Registry Backup]\t")
log.Printf("Intializing backup every %d seconds", interval)
select {
case <-time.Tick(time.Duration(interval) * time.Second):
    log.Println("Backing up node registry to disk")
    reg.write()
case <-killchan:
    log.Println("Flushing data to disk")
    reg.write()
    defer func() { killchan <- true }()
    //defer close(killchan)
    return
}

}

这是测试。我评论了添加时将冻结程序的行

nodeservice_test.go

func TestRunRegistry(t *testing.T) {
// need registry, interval, bool chan

// setup
fi, _ := ioutil.TempFile("", "msg-serv-test-")
defer os.Remove(fi.Name())
reg := MakeNodeRegistry(fi.Name())
name := "test"
ip := "10.0.0.1"
port := "0"
tls := false
node1 := MakeNode(name, ip, port, tls)
reg.addNode(node1)

interval := 5
kill := make(chan bool)

// test the interval
go runRegistryBackup(reg, interval, kill)
// let run for a little
time.Sleep(time.Minute) // adding this line hangs the whole program
// test kill
kill <- true
<-kill

actReg := BuildRegistry(fi.Name())

for key, eval := range reg.Nodes {
    val := actReg.Nodes[key]
    if eval != nil && val != nil {
        assert(t, *val, *eval)
    } else {
        t.Logf("Expected Hash: %d \t Expected Node: %v", key, eval)
        t.Logf("Key %d not found for Node %v", key, val)
        t.Fail()
    }

}

}

这是日志的输出,显示间隔只运行一次然后挂起。

[Registry Backup]       2019/07/19 13:29:51 Intializing backup every 5 seconds
[Registry Backup]       2019/07/19 13:29:56 Backing up node registry to disk

任何有关此问题原因的见解将不胜感激。

标签: go

解决方案


该函数runRegistryBackup调用write()一次然后退出。然后主 goroutine 阻止发送到kill.

通过向函数添加循环来修复。在循环之前创建一次代码,并在返回时停止代码。

func runRegistryBackup(reg *NodeRegistry, interval int, killchan chan bool) {
    log.SetPrefix("[Registry Backup]\t")
    log.Printf("Intializing backup every %d seconds", interval)
    t := time.NewTicker(time.Duration(interval) * time.Second)
    defer t.Stop()
    for {
        select {
        case <-t.C:
            log.Println("Backing up node registry to disk")
            reg.write()
        case <-killchan:
            log.Println("Flushing data to disk")
            reg.write()
            defer func() { killchan <- true }()
            return
        }
    }
}

无缓冲kill通道给程序增加了一些脆弱性。例如,如果runRegistryBackup修改为在写入错误时退出并且 main 尝试在该错误之后终止备份,则 main 将永远阻塞。通过使通道缓冲来修复。

kill := make(chan bool, 1)

推荐阅读