在golang中,是否可以优雅地停止计时器而不使用额外的通道?

huangapple go评论76阅读模式
英文:

Is it possible to stop timer gracefully without additional channel in golang?

问题

这是我的测试代码:

package main

import (
	"runtime"
	"testing"
	"time"
)

func testTimerRoutine(expire bool) bool {
	dur := 10 * time.Millisecond
	timer := time.NewTimer(dur)
	leak := true
	go func() {
		defer func() {
			println("timer routine quit")
			leak = false
		}()
		<-timer.C
		println("timer expired")
	}()

	if expire {
		time.Sleep(2 * dur)
	}

	if !timer.Stop() {
		println("timer stop false")
		<-timer.C
	} else {
		println("timer stop true")
	}

	runtime.GC()
	time.Sleep(10 * dur)
	return leak
}

func TestTimerRoutineDeadlock(t *testing.T) {
	leak := testTimerRoutine(true)
	if leak {
		t.Errorf("routine leak")
	}
}

func TestTimerRoutineLeak(t *testing.T) {
	leak := testTimerRoutine(false)
	if leak {
		t.Errorf("routine leak")
	}
}

我的意思是,不使用其他通道,只使用time.Timer.C

我现在启动一个专门用于读取timer.C的Go协程,以响应定时器启动后的超时。

但我发现这种方法存在两个问题:
1)如果定时器在超时之前被停止,似乎存在协程无法退出的问题;
2)如果定时器已经超时,并且调用Stop()返回false,在必须读取timer.C的API中会发生死锁。

我想知道是否有一种方法,在不使用额外通道的情况下,实现我需要的功能,即在定时器超时时提前停止定时器。

英文:

Here is my test code:

package main

import (
	&quot;runtime&quot;
	&quot;testing&quot;
	&quot;time&quot;
)

func testTimerRoutine(expire bool) bool {
	dur := 10 * time.Millisecond
	timer := time.NewTimer(dur)
	leak := true
	go func() {
		defer func() {
			println(&quot;timer routine quit&quot;)
			leak = false
		}()
		&lt;-timer.C
		println(&quot;timer expired&quot;)
	}()

	if expire {
		time.Sleep(2 * dur)
	}

	if !timer.Stop() {
		println(&quot;timer stop false&quot;)
		&lt;-timer.C
	} else {
		println(&quot;timer stop true&quot;)
	}

	runtime.GC()
	time.Sleep(10 * dur)
	return leak
}

func TestTimerRoutineDeadlock(t *testing.T) {
	leak := testTimerRoutine(true)
	if leak {
		t.Errorf(&quot;routine leak&quot;)
	}
}

func TestTimerRoutineLeak(t *testing.T) {
	leak := testTimerRoutine(false)
	if leak {
		t.Errorf(&quot;routine leak&quot;)
	}
}

I meant to think that without other channels, just using time.Timer.C.

I now start a go routine dedicated to reading timer.C in response to a timer timeout after the timer is started.

But I found two problems with this approach:

  1. there seems to be a problem with the go routine not exiting if the timer is stopped before it has timed out;
  2. If the timer has already timed out and the call to Stop() returns false, there will be a deadlock at this point to drain timer.C as required by the API.

I wonder if there is a way, without using an additional Channel, to achieve the functionality I require to stop the timer early in response to a timer timeout.

答案1

得分: 0

这是来自计时器文档的一段引用:

这个(Stop)不能与从计时器通道接收的其他内容或对计时器的Stop方法的其他调用同时进行。

然而,这正是你正在做的。你有一个goroutine与Stop调用同时从计时器通道接收。

一般来说,如果你需要停止计时器,因为某种事件发生了,发送一个请求给监听的goroutine来停止计时器。

你不需要清空通道,因为它是一个容量为1的通道,所以如果没有任何人从中接收数据,它会被垃圾回收。

英文:

Here's a quote from the documentation of timer:

> This (Stop) cannot be done concurrent to other receives from the Timer's channel or other calls to the Timer's Stop method.

Yet, this is exactly what you are doing. You have a goroutine receiving from a timer channel concurrently with the Stop call.

In general, if you need to stop a timer because of some sort of an event, send a request to the listening goroutine to stop the timer.

You don't have to drain the channel as it is a channel with capacity 1, so if nothing is listening from it, it will be garbage collected.

huangapple
  • 本文由 发表于 2023年3月27日 10:15:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/75851661.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定