Go
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions and Methods
-
Section 4: Concurrency
-
Section 5: Packages and Tooling
-
Section 6: More Standard Library
-
Section 7: Building Services
-
Section 8: Advanced Go
-
Section 9: Go in the Cloud-Native Ecosystem
-
Section 10: Data Structures and Algorithms in Go
-
Section 11: Testing and Deployment
-
Section 12: Practical Projects
-
Section 13: More Standard Library Practice
-
Section 14: More Practice Projects
-
Section 15: Design Patterns in Go
-
Section 16: Interview Practice
-
Section 17: Package fmt In Depth
-
Section 18: Package strings and strconv
-
Section 19: Package os and io
-
Section 20: Package time
-
Section 21: Package sort and container
-
Section 22: Package encoding
-
Section 23: Package net/http In Depth
-
Section 24: Package context
-
Section 25: Package regexp and bytes
-
Section 26: Package errors In Depth
-
Section 27: Package crypto and hash
-
Section 28: Package flag and log
-
Section 29: Package sync In Depth
-
Section 30: More Practice Exercises
-
Section 31: Go Modules and Workspaces In Depth
-
Section 32: Generics Deep Dive (Go 1.18+)
-
Section 33: Testing Package In Depth
-
Section 34: More Interview and Whiteboard Practice
-
Section 35: Package math and unicode
-
Section 36: Package path and filepath
-
Section 37: Package database/sql
-
Section 38: Package text/template and html/template
-
Section 39: Package archive and compress
-
Section 40: Lower-Level net Package
-
Section 41: Package runtime and reflect
-
Section 42: Package embed and io/fs
-
Section 43: Go Assembly and CGO Basics
-
Section 44: Building CLIs and TUIs
-
Section 45: Go Performance Tuning
-
Section 46: More Real-World Projects
-
Section 47: Go in Production
-
Section 48: Go Design Patterns
-
Section 49: Go Interfaces Deep Dive
-
Section 50: Final Practice and Review
124: Timers and Tickers
Imagine you're in a kitchen. You have two tools for tracking time: a mechanical egg timer and a metronome. The egg timer is a "one-shot" deal. You twist it to three minutes, walk away, and when it dings, you're done. You don't expect it to ding again every three minutes for the rest of the afternoon. The metronome, however, is different. Once you start it, it clicks at a steady, rhythmic interval—every second, every second, every second—until you physically reach out and stop it.
In Go, time.Timer is your egg timer, and time.Ticker is your metronome. Both use channels to tell your code that time is up, but they serve very different architectural purposes.
The One-Shot Deal with Timers
Most of the time, you'll use a timer to handle timeouts. I've seen countless bugs where a program hangs forever because a network request never returned. That's where timers save you. If you just need a quick "do this after X duration," time.After is the shorthand way to go. It returns a channel that sends a single value after the time elapses.
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("Waiting for the 'egg timer' to ding...")
// time.After is great for simple select statements
select {
case <-time.After(2 * time.Second):
fmt.Println("Time's up! The egg is boiled.")
}
}
But here is the nuance: if you need to be able to stop the timer or reset it before it expires, time.After won't work because it doesn't give you a handle to the timer object. In those cases, you use time.NewTimer. This gives you a struct that allows you to call Stop(), which is crucial for preventing resource leaks in high-performance loops.
Steady Pulses with Tickers
Now, let's talk about the metronome. You use a time.Ticker when you have a recurring task—like sending a heartbeat signal to a server every 30 seconds or refreshing a cache every minute. Unlike a timer, a ticker's channel will fire repeatedly at the interval you specify.
package main
import (
"fmt"
"time"
)
func main() {
// Create a ticker that clicks every 500 milliseconds
ticker := time.NewTicker(500 * time.Millisecond)
// I'm using a channel to tell the loop when to stop
done := make(chan bool)
go func() {
for {
select {
case <-done:
return
case t := <-ticker.C:
fmt.Println("Tick at", t)
}
}
}()
time.Sleep(2 * time.Second)
done <- true
fmt.Println("Stopped the metronome.")
}
Don't Forget to Stop the Clock
Here is a piece of advice I give every junior dev: always stop your tickers. I've seen production environments slowly eat up memory because developers created tickers inside a function, forgot to call ticker.Stop(), and left them running in the background long after the function returned.
The garbage collector can't always clean up a ticker because the runtime is still actively sending pulses to that channel. Whenever you use NewTicker or NewTimer, you should almost always defer ticker.Stop() immediately after the error check. It's a simple habit that saves you from a nightmare debugging session three weeks later.
📋 Practical Task
Building a Heartbeat Monitor with Timeouts
You need to build a "Heartbeat Monitor" that simulates a connection to a remote server. Your program should implement the following logic:
- Create a
time.Tickerthat triggers every 2 seconds. Every time it ticks, it should attempt to "ping" the server (just print "Sending heartbeat..."). - For each ping, create a
time.Timerthat acts as a timeout. If the "server" (which you will simulate using a random delay) doesn't respond within 1 second, print "Warning: Heartbeat timed out!". - The simulation should run for a total of 10 seconds and then shut down gracefully, ensuring the ticker is stopped.
Requirement: Use a select block to handle both the ticker's pulse and the timer's timeout. Use time.Sleep with a random duration to simulate the variable response time of the server.
There are no comments for now.