服务器执行函数时如何避免Ticker泄漏 - Golang实践

服务器执行函数时如何避免Ticker泄漏 - Golang实践 你好,我有一个这样的问题。一般来说,有一个服务器会处理某个函数,这个函数设置了一个定时器(ticker)。当这个函数结束时,会调用 ticker.Stop() 来停止它。问题在于,如果我不是从服务器运行这个函数,一切正常,定时器会随着函数执行完毕而停止。但是,当从服务器启动时,定时器会无限期地泄漏。

定时器函数本身:

func cmdTicker() {
ticker := time.NewTicker(time.Second)
now := time.Now()

for range ticker.C {
	fmt.Println(fmt.Sprintf("%s", time.Since(now)))
	//timeProc = append(timeProc, fmt.Sprintf("%s", time.Since(now)))
}

ticker.Stop()
}

定时器启动:

func Scan(cmd *pb.ScanCommandReq) []*models.Cmdprun {

wgAllScan := new(sync.WaitGroup)
//while the function is running ticker goes
go cmdTicker()

go func(wg *sync.WaitGroup) {
	for res := range resultsCh {
		if res.stateProc == 0 {
			host, err := resultfiles.ParseXMLResultFile(res.fileScan)
			if err != nil {
				log.Printf("Parsing file not done: %s", err)
			}

			results = append(results, host)

			err = os.Remove(res.fileScan)
			if err != nil {
				log.Fatal("Can't delete file", res.fileScan, err)
			}
		}
		wg.Done()
	}

}(wgAllScan)

wgAllScan.Wait()
close(resultsCh)

return results
}

更多关于服务器执行函数时如何避免Ticker泄漏 - Golang实践的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

我看到你的问题时也想到了同样的事情,但我之前没来得及早点回复你!很高兴你自己解决了问题并分享了解决方案 👍

更多关于服务器执行函数时如何避免Ticker泄漏 - Golang实践的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


当面临类似任务时,有人能派上用场。通过检查以从通道获取值并停止计时器来解决此问题。

func cmdTicker() {
   ticker := time.NewTicker(time.Second)
   now := time.Now()

for {
	// Select 语句
	select {
	// Case 语句
	case <-chTicker:
		ticker.Stop()
		fmt.Println("扫描完成!")
		return

	// 打印计时器的 Case
	case <-ticker.C:
		fmt.Println(fmt.Sprintf("%s", time.Since(now)))
	}
}
}

在函数本身中,我们发送一个成功完成的信号。

chTicker <- true

在服务器环境中,cmdTicker() 函数中的 ticker.Stop() 永远不会被执行,因为 for range ticker.C 会无限循环。你需要通过上下文或通道来控制定时器的退出。以下是修复后的代码示例:

func cmdTicker(ctx context.Context) {
    ticker := time.NewTicker(time.Second)
    defer ticker.Stop() // 确保退出时停止
    
    now := time.Now()
    
    for {
        select {
        case <-ctx.Done():
            return // 上下文取消时退出
        case <-ticker.C:
            fmt.Printf("%s\n", time.Since(now))
        }
    }
}

启动定时器时需要传入上下文:

func Scan(cmd *pb.ScanCommandReq) []*models.Cmdprun {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel() // 函数结束时取消上下文
    
    wgAllScan := new(sync.WaitGroup)
    
    // 传入上下文控制定时器退出
    go cmdTicker(ctx)
    
    go func(wg *sync.WaitGroup) {
        for res := range resultsCh {
            if res.stateProc == 0 {
                host, err := resultfiles.ParseXMLResultFile(res.fileScan)
                if err != nil {
                    log.Printf("Parsing file not done: %s", err)
                }
                
                results = append(results, host)
                
                err = os.Remove(res.fileScan)
                if err != nil {
                    log.Printf("Can't delete file %s: %v", res.fileScan, err)
                }
            }
            wg.Done()
        }
    }(wgAllScan)
    
    wgAllScan.Wait()
    close(resultsCh)
    
    return results
}

或者使用通道控制退出:

func cmdTicker(stopCh <-chan struct{}) {
    ticker := time.NewTicker(time.Second)
    defer ticker.Stop()
    
    now := time.Now()
    
    for {
        select {
        case <-stopCh:
            return
        case <-ticker.C:
            fmt.Printf("%s\n", time.Since(now))
        }
    }
}

// 使用方式
stopCh := make(chan struct{})
go cmdTicker(stopCh)
// 需要停止时:close(stopCh)

关键点:

  1. 使用 defer ticker.Stop() 确保资源释放
  2. 通过 select 语句同时监听定时器和退出信号
  3. 服务器函数结束时发送退出信号(上下文取消或关闭通道)
回到顶部