Goroutine: time.Sleep or time.After

Per go101

The two will both pause the current goroutine execution for a certain duration. The difference is the function call time.Sleep(d) will let the current goroutine enter sleeping sub-state, but still stay in running state, whereas, the channel receive operation <-time.After(d) will let the current goroutine enter blocking state.


Update 08/15/2022

The difference from go dev

  • time.After
 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.
  • time.Sleep
Sleep pauses the current goroutine for at least the duration d. A negative or zero duration causes Sleep to return immediately.

As @cnicutar pointed out that

After is useful in contexts where one already needs to select on a number of channels but would also like a timeout

Here is another details example to compare time.After with time.Sleep within select

var wg sync.WaitGroup

func cancelAfter(interval time.Duration, cancel context.CancelFunc) {
    go func(cancel context.CancelFunc) {
        time.Sleep(interval)
        fmt.Println("cancel task at ", time.Now())
        cancel()
        wg.Done()
    }(cancel)
}

func TestTimerSleep(t *testing.T) {
    ctx, cancel := context.WithCancel(context.Background())
    wg.Add(1)
    cancelAfter(time.Second*15, cancel)

    wg.Add(1)
    go func(ctx context.Context) {
        for {
            select {
            case <-ctx.Done():
                fmt.Println("TimerSleep is cancelled", time.Now())
                wg.Done()
                return
            default:
                time.Sleep(time.Second * 6)
                fmt.Println("time sleep", time.Now())
            }
        }
    }(ctx)
    wg.Wait()
}

func TestTimerAfter(t *testing.T) {
    ctx, cancel := context.WithCancel(context.Background())
    wg.Add(1)
    cancelAfter(time.Second*15, cancel)

    wg.Add(1)
    go func(ctx context.Context) {
        for {
            select {
            case <-ctx.Done():
                fmt.Println("TimerAfter is cancelled", time.Now())
                wg.Done()
                return
            case <-time.After(time.Second * 6):
                fmt.Println("time after", time.Now())
            }
        }
    }(ctx)
    wg.Wait()
}

Results

TestTimerSleep

time sleep 2022-08-15 15:43:56.502134 
time sleep 2022-08-15 15:44:02.502682 
cancel task at  2022-08-15 15:44:05.501721 
time sleep 2022-08-15 15:44:08.503892 
TimerSleep is cancelled 2022-08-15 15:44:08.50393 


TestTimerAfter
time after 2022-08-15 15:44:55.089277
time after 2022-08-15 15:45:01.0905
cancel task at  2022-08-15 15:45:04.089352
TimerAfter is cancelled 2022-08-15 15:45:04.089432

From the results, the time.After could be canceled immediately when time out.


I don't think it matters much for the majority of programs. There has been a question on golang-nuts about this but I don't think one can draw any conclusion.

In practice After is useful in contexts where one already needs to select on a number of channels but would also like a timeout:

select {
case c := <-someChan:
  ..
case c := <-otherChan:
  ..
case <-time.After(time.Second * 42):
}

By superficially looking at the code Sleep seems simpler while After builds a new timer, with a period, a closure to send the time when it finishes etc.

Again, I don't think it matters in practice but time.Sleep seems pretty readable so I would go with that.


On my implementation both of them perform the exact same system calls and end up waiting:

futex(??, FUTEX_WAIT, 0, {41, 999892351}
                          ^^ 41 seconds and change

Tags:

Go