英文:
Do tickers in golang overwrite already running processes?
问题
例如,如果我有这样的代码:
t := time.NewTicker(time.Second*1)
for {
select {
case <-t.C:
runSomeExpensiveFunction() // 耗时超过1秒的函数
}
// ...
}
假设runSomeExpensiveFunction
在一个循环中打印从1到1万亿的数字。
runSomeExpensiveFunction
是否保证会完成运行,还是每次收到新的tick时,Golang会终止它并重新运行?
我进行了一些手动测试,似乎golang会等待这个函数完成后再进行下一个tick,但这感觉不对。
英文:
For example, if I have something like:
t := time.NewTicker(time.Second*1)
for {
select {
case <-t.C:
runSomeExpensiveFunction() // takes over 1 second
}
// ...
}
And let's say runSomeExpensiveFunction
prints numbers from 1 to 1 trillion in a loop.
Is runSomeExpensiveFunction
guaranteed to finish running or will Golang kill it and run it again everytime a new tick is received?
I've done some manual testing and it seems that golang will wait for this function to finish before going onto the next tick, but this feels wrong to me.
答案1
得分: 2
首先,Go语言不会随意终止一个goroutine。runSomeExpensiveFunction
函数会一直运行直到完成。
其次,根据我们所看到的代码,只会同时运行一个runSomeExpensiveFunction
函数。将其视为“每次接收到一个新的tick”是不正确的,因为没有任何东西在“接收”tick。你的select
语句只会执行一个分支,然后结束,是for { }
循环导致select
语句被多次执行,直到runSomeExpensiveFunction
函数返回时才会到达for
循环的末尾。
你的select
语句会调用runSomeExpensiveFunction
函数,执行流程会进入该函数,然后select
语句就结束了。计时器可能会在其通道上发送下一个tick,但没有任何东西在监听它。只有当函数返回时,执行流程才会到达for { }
循环的末尾,然后返回到顶部,遇到select
语句,测试读取计时器通道的case
分支。
如果你想要同时运行多个函数调用,那就要使用go
关键字:
case <-t.C:
go runSomeExpensiveFunction()
需要注意的是,如果该函数执行时间超过一秒,你将面临无限并行性,并且程序最终会崩溃。
英文:
First, Go will never just kill a go routine. runSomeExpensiveFunction
will run until it finishes.
Secondly, only one runSomeExpensiveFunction
will ever run at a time given the code we can see here. It's incorrect to think of this as "everytime a new tick is received", because nothing is "receiving" a tick. Your select
executes one branch and then it's done, it's the for { }
that causes the select
to be executed many times, and the end of the for
isn't reached until runSomeExpensiveFunction
returns.
Your select
will invoke runSomeExpensiveFunction
, and the flow of execution will enter that function, and the select
is done. The timer might emit its next tick over its channel, but nothing is listening for it. It's only when your function returns that the flow of execution hits the end of your for { }
and returns to the top, where it encounters the select
again, and the case
that reads from the timer's channel is tested.
If you want to have multiple invocations of your function running concurrently, that's what the go
keyword is for:
case <-t.C:
go runSomeExpensiveFunction()
You should note that, if the function takes more than a second, you'll be looking at unbounded parallelism and your program will eventually crash.
答案2
得分: 2
t.C
不是魔法。t.C
返回一个通道。<-t.C
尝试从通道中读取数据。该通道会阻塞一段时间,然后返回一个时间。
select
会选择第一个没有阻塞的通道。在你的代码中,它会等待 t.C
返回一个 tick,然后运行 runSomeExpensiveFunction
。
例如,以下代码会打印出 1、2,然后超时。
func handle(a int) {
time.Sleep(time.Second*2)
fmt.Println(a)
}
func main() {
t := time.NewTicker(time.Second*1)
c := make(chan int, 2)
c<-1
c<-2
for {
select {
case m := <-c:
handle(m)
case <-t.C:
fmt.Println("timed out")
}
}
}
select
尝试从c
和t.C
中读取数据。c
立即返回,所以handle
函数会运行。select
尝试从c
和t.C
中读取数据。c
立即返回,所以handle
函数会运行。select
尝试从c
和t.C
中读取数据。c
是空的,所以<-c
会阻塞。<-t.C
会阻塞一秒钟,然后返回一个时间并打印出 "timed out"。
然后重复步骤 3。
英文:
t.C
isn't magic. t.C
returns a channel. <-t.C
tries to read from the channel. The channel blocks for the duration and then returns a Time.
select
picks the first channel which isn't blocking. In your code it will wait until t.C
returns a tick and then run runSomeExpensiveFunction
.
For example, this will print 1, 2, then time out.
func handle(a int) {
time.Sleep(time.Second*2)
fmt.Println(a)
}
func main() {
t := time.NewTicker(time.Second*1)
c := make(chan int, 2)
c<-1
c<-2
for {
select {
case m := <-c:
handle(m)
case <-t.C:
fmt.Println("timed out")
}
}
}
select
tries to read fromc
andt.C
.c
returns immediately sohandle
runs.select
tries to read fromc
andt.C
.c
returns immediately sohandle
runs.select
tries to read fromc
andt.C
.c
is empty so<-c
blocks.<-t.C
blocks for a second and returns a Time and printstimed out
.
And it repeats step 3.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论