golang中的计时器与select

x33g5p2x  于2021-11-15 转载在 Go  
字(1.9k)|赞(0)|评价(0)|浏览(393)

golang中的select机制让我对大师的设计水平赞叹不已,它基于多个阻塞的通道操作(发送或接收),哪个可以执行,就执行哪个。

select {
case <- ch1:
	// ...
case x <- ch2:
	// use x ...
case ch3 <- y:
	// ...
}

前两个case是接收,第三个case是发送。golang的运行机制是,当前的goroutine会阻塞,当ch1或ch2可以接收事件时,或者ch3可以发送事件时,就触发对应的case代码块,然后select块结束。否则将一直阻塞。

一个空的select {}将使当前的goroutine永久阻塞。

如果不希望select阻塞,则可以设置一个default分支,在所有的case都阻塞地情况下,将会运行default分支。

示例1

这个示例来自官网的demo https://tour.golang.org/concurrency/5

func fibonacci(c, quit chan int) {
	x, y := 0, 1
	for {
		select {
		case c <- x:
			x, y = y, x+y
		case <-quit:
			fmt.Println("quit")
			return
		}
	}
}

func main() {
	c := make(chan int)
	quit := make(chan int)
	go func() {
		for i := 0; i < 10; i++ {
			fmt.Println(<-c)
		}
		quit <- 0
	}()
	fibonacci(c, quit)
}

fibonacci函数中,一个死循环中包含一个select,也就是意味着,每次当c通道的事件被接收一次,此时通道就允许发送一个新事件,对应的case代码块就运行一次,直到quit有了新的事件,此时退出死循环。

在main函数中启动了一个独立的goroutine来运行匿名函数,这个匿名函数接收10次c通道的事件,导致fibonacci函数的第一个case条件触发了10次,然后,匿名函数向quit通道发送了一个事件,从而结束掉fibonacci的死循环,此时main函数也结束了。

计时器

golang的计时器很适合和select语句搭配使用。

tick := time.Tick(time.Second)

上面代码中的 tick 是一个chan time.Time类型的通道,这个通道中会每秒产生一个事件,事件的内容是当前时间。时间间隔是由time.Tick()的参数指定的。

tick通道没有缓冲,这意味着如果tick通道中的事件被接收了,那么1秒之后还会再产生一个新的事件,但如果不接收的话,新的事件无法被发送到通道中。

通过tick.Stop()来停止计时器,否则可能造成goroutine泄露。

select {
case <-time.After(time.Second * 5):
	fmt.Println("bingo")
}

上面的time.After,返回一个通道,这个通道在指定的时间间隔之后将会产生一个事件。

上面的代码将在5秒钟之后打印一句bingo。

计时器+select的示例

下面的代码来自《GO程序设计语言》。它实现的功能是,10秒钟之后将会触发一个事件(导弹发射),每秒钟打印一个数字,但是,如果中间用户敲击了一次键盘,则程序终止。

func main() {
	abort := make(chan struct{})
	go func() {
		os.Stdin.Read(make([]byte, 1))
		abort <- struct{}{}
	}()

	tick := time.Tick(time.Second)
	for countdown := 10; countdown >= 0; countdown-- {
		select {
		case <-tick:
			fmt.Println(countdown)
		case <-abort:
			fmt.Println("launch aborted!")
			return
		}
	}
	fmt.Println("launch!")
}

这就是一个典型的循环+select的代码结构,循环中的select监听两个通道,第一个通道来自time.Tick,每秒钟产生一个事件,有一次的运行机会,另一个通道来自用户输入。当循环结束时,将会执行最后一行代码,当用户取消时,直接退出main函数。

在循环中select一个tick通道,每秒钟最多循环一次,就像具备time.Sleep(time.Second)的效果一样。

相关文章

最新文章

更多