Timer changes in Go 1.23

It is difficult to achieve a spirit of genuine cooperation as long as people remain indifferent to the feelings and happiness of others.
— Dalai Lama

Medium Link: Timer changes in Go 1.23:Enhancing Accuracy and Simplifying Concurrency | by Wesley Wei | Jul, 2024 | Programmer’s Career

Hello, here is Wesley, Today’s article is about timer changes in Go 1.23. Without further ado, let’s get started.💪

Go 1.23 includes a new implementation of channel-based timers created by time.NewTimer, time.After, time.NewTicker, and time.Tick.

Enhanced Garbage Collection

  1. In previous Go versions (before 1.23), if a program did not explicitly stop using the Stop method, unstopped time.Timers and time.Tickers would remain in memory. This could lead to resource leaks, especially in long-running programs.
  2. Go 1.23 resolves this issue by allowing unreferenced time.Timers and time.Tickers to be garbage-collected immediately. This ensures efficient memory management and prevents unnecessary resource usage.

It’s a good habit to use defer or other methods to call Stop explicitly, but if you forget to do so, it may lead to irreversible consequences.

Go has further strengthened the robustness of code by making this change, which is definitely commendable. If you call the NewTicker method in Go 1.23, you’ll see a comment:

1
2
3
4
5
6
7
8
9
10
11
// Before Go 1.23, the garbage collector did not recover
// tickers that had not yet expired or been stopped, so code often
// immediately deferred t.Stop after calling NewTicker, to make
// the ticker recoverable when it was no longer needed.
// As of Go 1.23, the garbage collector can recover unreferenced
// tickers, even if they haven't been stopped.
// The Stop method is no longer necessary to help the garbage collector.
// (Code may of course still want to call Stop to stop the ticker for other reasons.)
func NewTicker(d Duration) *Ticker {

}

Source: src/time/tick.go - Go at Google

Channel Synchronization

  • In previous Go versions (before 1.23), the timer channels for time.Timer and time.Ticker had a buffer capacity of 1. This could lead to challenges when using Reset or Stop methods, as calling them might pass outdated values.
  • Go 1.23 introduces synchronized timer channels (unbuffered, with a capacity of 0). This effectively prevents further sending or receiving signals on the channel after calling Reset or Stop. This simplifies reasoning about timer behavior and eliminates potential issues with outdated values.

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package main

import (
"fmt"
"time"
)

func main() {
timer := time.NewTimer(2 * time.Second)
go func() {
select {
case <-timer.C:
fmt.Println("Timer expired")
case <-time.After(1 * time.Second):
fmt.Println("Timeout waiting for timer")
}
}()

time.Sleep(3 * time.Second) // Main goroutine sleeps for 3 seconds
if !timer.Stop() { // Check if timer was stopped
<-timer.C // Try to read from the timer's channel (goroutine 2)
fmt.Println("Timer was stopped but had to read expired value")
} else {
fmt.Println("Timer was stopped successfully")
}
}

Output:
// 1.22
Timeout waiting for timer
Timer was stopped but had to read expired value

// 1.23
Timeout waiting for timer
Timer was stopped successfully

Run Code in Go Dev

Impact on Existing Code

Compatibility

  • The new timer behavior will only be enabled when the program’s go.mod file specifies Go 1.23.0 or a later version. If you’re building older code with Go 1.23, the previous timer behavior will still apply.
  • To restore asynchronous channel behavior (even if using Go 1.23 or later), you can set GODEBUG: GODEBUG=asynctimerchan=1.

Channel Length

If your code relies on checking the length of a channel (using len) to determine whether a receive operation is successful, you’ll need to modify it to use non-blocking receives. Since channels are now unbuffered, len and cap will always return 0.

For example, in Go 1.23, you would need to change the following logic:

1
2
3
4
5
6
7
8
9
10
11
12
func main() {
timer := time.NewTimer(2 * time.Second)

time.Sleep(1 * time.Second)
if len(timer.C) > 0 {
fmt.Println("Timer channel has a value")
} else {
fmt.Println("Timer channel is empty")
}

timer.Stop()
}

to:

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
timer := time.NewTimer(2 * time.Second)

time.Sleep(1 * time.Second)
select {
case <-timer.C:
fmt.Println("Timer channel has a value")
default:
fmt.Println("Timer channel is empty")
}

timer.Stop()
}

Scheduling Delay

Before Go 1.23, when creating a timer, there was a scheduling delay involved. The latest timer implementation no longer takes into account this scheduling delay, so this change may also affect existing code.

For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import (
"time"
)

func main() {
for i := 1; i < 10; i++ {
c := make(chan bool)
close(c)

select {
case <-c:
println("done")
case <-time.After(0 * time.Nanosecond):
println("timeout")
}
}
}

One output:

  • Go 1.22: done (9 times)
  • Go 1.23: done, timeout, done,done, done, timeout, timeout, timeout, timeout

Run Code in Go Dev

Summary

  • Improved garbage collection for channel-based timers that are not referenced.
  • Asynchronous timer channels have been changed to synchronous timer channels.
  • Scheduling delay when creating channel-based timers has been eliminated.

In summary, these changes will improve the behavior of timers in Go 1.23, promoting better resource management and clearer Reset and Stop semantics.

However, they may also introduce potential issues if you encounter compatibility problems with old code; consider using GODEBUG settings or updating your code to handle unbuffered (blocking) channels properly.

References

Go Wiki: Go 1.23 Timer Channel Changes - The Go Programming Language


More Series Articles about Golang Forward-looking:

https://wesley-wei.medium.com/list/golang-forwardlooking-12998e22f7eb

And I’m Wesley, delighted to share knowledge from the world of programming. 

Don’t forget to follow me for more informative content, or feel free to share this with others who may also find it beneficial. it would be a great help to me.

See you in the next article. 👋

中文文章: https://programmerscareer.com/zh-cn/golang-timer-changes/
Author: Wesley Wei – Twitter Wesley Wei – Medium
Note: Originally written at https://programmerscareer.com/golang-timer-changes/ at 2024-07-06 23:39. If you choose to repost or use this article, please cite the original source.

WaitGroup You Should Know in Golang Pointers You Should Know in Golang

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×