How to use 'time.After' and 'default' in Golang

My computer develop environment

1
2
3
4
5
6
$ sw_vers 
ProductName: Mac OS X
ProductVersion: 10.12.3

$ go version
go version go1.11.1 darwin/amd64

Interrupted by signal

In the process of using goroutine, for{} is often used to maintain some long-term work. We will control the exit by sending signals to control the goroutine.

1
2
3
select {
case <-ch:
}

When ch channel receives a signal, the for{} loop can be terminated by break.

1
2
3
4
5
6
7
8
9
10
11
12
13
loop:
for {
select {

case <-stopChan:
log.Println("Receive stop sign, stop loop. ")
break loop
}
case <-ch2:
// do something
}
...
}

When there are multiple case, select will randomly select a case to execute. At this time, select will block, until that the selected case is finished, and other case will not be executed.
But it’s easy to see the code in the selected case run for a long time, which seriously affects the execution of other case. This time you need to have timeout logic.

Timeout interrupt loop

In some scenarios, the loop should be time controlled. For example, in one of the cases, the network request timed out.

1
2
3
4
5
6
for {
select {
case <-ch:
// http get timeout
}
}

Of course, you can also add a timeout attribute to the http request to ensure the timeliness of the network request. But here we use time.After to complete it.
First look at its structure

1
2
3
4
5
6
7
8
9
// After waits for the duration to elapse and then sends the current time
// on the returned channel.
// It is equivalent to NewTimer(d).C.
// The underlying Timer is not recovered by the garbage collector
// until the timer fires. If efficiency is a concern, use NewTimer
// instead and call Timer.Stop if the timer is no longer needed.
func After(d Duration) <-chan Time {
return NewTimer(d).C
}

This function will return a channel, which is exactly what select{} case needs.
time.After() returns a channel message of type Time.
Based on this function, it is equivalent to the implementation of the timer, and is non-blocking.

Then the code should look like this:

1
2
3
4
5
6
7
8
loop:
for {
select {
case <-time.After(60 * time.Second):
log.Println("Time to stop. ")
break loop
}
}

Exiting the loop after 60 seconds, it looks very simple, right?

‘time.After’ and ‘default’ used together

In select{}, not only case, but also a default keyword.

1
2
3
4
select {
default:
// do default thing
}

Go back to the previous example, if the limit loop is executed for up to 1 minute.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
loop:
for {
select {
case <-ch1:
// do something 1
case <-ch2:
// do something 2

case <-time.After(60 * time.Second):
log.Println("Time %d to stop. ")
break loop

default:
// do default thing
}
}

The code doesn’t seem to have any problems, but when executed, it will be found that the loop will not be aborted!
case <-time.After(60 * time.Second): has no work!
Why?

We can see inside the time.After function, which is actually return a new timer channel.
In the code above, each time you execute time.After(60 * time.Second) you create a new timer channel.
There’s no way the select statement can remember the channel it selected on in the previous iteration.

Now that you know the reason, then the next step is how to update these code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
timeout := time.After(60 * time.Second)
loop:
for {
select {
case <-ch1:
// do something 1
case <-ch2:
// do something 2

case <-timeout:
log.Println("Time %d to stop. ")
break loop

default:
// do default thing
}
}

Very simple, right?
select default is blocked, define a timer type channel outside the loop, this time select{} is also fair to select one of the channel to execute.

Share