Home > Software engineering >  Do tickers in golang overwrite already running processes?
Do tickers in golang overwrite already running processes?

Time:03-05

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.

CodePudding user response:

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")
    }
  }
}
  1. select tries to read from c and t.C. c returns immediately so handle runs.
  2. select tries to read from c and t.C. c returns immediately so handle runs.
  3. select tries to read from c and t.C. c is empty so <-c blocks. <-t.C blocks for a second and returns a Time and prints timed out.

And it repeats step 3.

CodePudding user response:

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 be 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.

  •  Tags:  
  • go
  • Related