Go > Concurrency > Concurrency Patterns > Ticker and Timer

Go Ticker and Timer Example

Demonstrates the use of time.Ticker and time.Timer in Go for scheduling tasks at regular intervals and after a specific delay.

Introduction to Tickers and Timers

In Go, time.Ticker and time.Timer are essential tools for managing time-based events in concurrent programs. A time.Ticker triggers events at regular intervals, while a time.Timer triggers an event once after a specified duration. They are widely used for tasks like periodic updates, timeouts, and scheduling operations.

Ticker Example: Periodic Task Execution

This example creates a time.Ticker that sends a value on its channel C every second. The select statement waits for either a tick from the ticker or a timeout. The ticker prints a message each time it ticks, and the program exits after 5 seconds. The defer ticker.Stop() call ensures that the ticker is stopped when the program finishes, preventing resource leaks.

package main

import (
	"fmt"
	"time"
)

func main() {
	// Create a new ticker that ticks every 1 second
	ticker := time.NewTicker(1 * time.Second)
	defer ticker.Stop() // Ensure the ticker is stopped when the function exits

	// Run for 5 seconds
	timeout := time.After(5 * time.Second)

	for {
		select {
		case <-ticker.C:
			fmt.Println("Tick at", time.Now().Format(time.RFC3339))
		case <-timeout:
			fmt.Println("Timeout! Exiting...")
			return
		}
	}
}

Explanation of the Code

  • time.NewTicker(1 * time.Second): Creates a new ticker that will send the current time on its channel every second.
  • defer ticker.Stop(): Stops the ticker when the main function returns. This is important to prevent the ticker from continuing to send events indefinitely.
  • time.After(5 * time.Second): Creates a channel that will receive a value after 5 seconds.
  • select: Waits for either a tick from the ticker's channel (ticker.C) or a signal from the timeout channel.
  • fmt.Println("Tick at", time.Now().Format(time.RFC3339)): Prints a message each time the ticker ticks.
  • fmt.Println("Timeout! Exiting..."): Prints a message when the timeout occurs and then exits the program.

Timer Example: Delayed Task Execution

This example creates a time.Timer that will send a value on its channel C after 3 seconds. The program blocks until the timer fires, then prints a message. defer timer.Stop() will stop the timer if it has not already fired, preventing a potential resource leak. This is important if the surrounding logic can exit before the timer fires.

package main

import (
	"fmt"
	"time"
)

func main() {
	// Create a new timer that will fire after 3 seconds
	timer := time.NewTimer(3 * time.Second)
	defer timer.Stop() // Ensure the timer is stopped if it's no longer needed

	fmt.Println("Waiting for timer...")

	<-timer.C // Block until the timer fires

	fmt.Println("Timer fired at", time.Now().Format(time.RFC3339))
}

Explanation of the Code

  • time.NewTimer(3 * time.Second): Creates a new timer that will send the current time on its channel after 3 seconds.
  • defer timer.Stop(): Stops the timer when the main function returns, preventing the timer from potentially sending an event after the program has finished its primary task. This is especially important if you're using timers in functions that might return early under certain conditions.
  • <-timer.C: Blocks until the timer's channel receives a value, signaling that the timer has fired.
  • fmt.Println("Timer fired at", time.Now().Format(time.RFC3339)): Prints a message indicating when the timer fired.

Concepts Behind the Snippets

time.Ticker and time.Timer are built upon Go's concurrency model using channels. They provide a way to send signals across goroutines at specific times or intervals. The Stop() method is crucial for preventing resource leaks, especially when the ticker or timer might not fire before the program exits or the surrounding function returns. Always remember to handle the potential for timers or tickers that are no longer needed.

Real-Life Use Case Section

Tickers: Polling a database for changes at regular intervals, updating a UI with live data every few seconds, sending heartbeat signals to a server.

Timers: Implementing timeouts for network requests, retrying an operation after a delay, scheduling a cleanup task to run after a certain period of inactivity.

Best Practices

  • Always call Stop() on tickers and timers when they are no longer needed to prevent resource leaks.
  • Use select statements to handle timeouts and avoid blocking indefinitely.
  • Consider the precision requirements of your application when choosing between tickers and timers. Tickers are generally less precise than timers, especially at high frequencies.

Interview Tip

Be prepared to explain the difference between time.Ticker and time.Timer, and how they can be used in concurrent programs. Also, be ready to discuss the importance of stopping tickers and timers to prevent resource leaks and the use of select for handling timeouts.

When to use them

Ticker: Use when you need to perform an action repeatedly at regular intervals.

Timer: Use when you need to perform an action once after a specified delay.

Memory footprint

Both time.Ticker and time.Timer consume resources, including a goroutine and a channel. Stopping them releases these resources. Not stopping them can lead to memory leaks, especially in long-running applications.

Alternatives

Instead of using Tickers directly, consider using libraries that abstract away the complexities of managing goroutines and channels for scheduled tasks. For simpler scenarios, you could also implement your own polling mechanisms with time.Sleep, but this is generally less efficient and harder to manage for complex scheduling requirements.

Pros

  • Built-in: Part of the standard Go library.
  • Simple to use: Easy to create and use tickers and timers.
  • Concurrency-safe: Designed to work with Go's concurrency model.

Cons

  • Resource usage: Requires a goroutine and a channel, which can consume resources if not managed properly.
  • Requires explicit stopping: Must be explicitly stopped to prevent resource leaks.
  • Limited precision: Not suitable for tasks that require very high precision timing.

FAQ

  • What happens if I don't call Stop() on a ticker?

    The ticker will continue to send events on its channel, even after you no longer need them. This can lead to resource leaks and unexpected behavior.
  • Can I reset a timer?

    Yes, you can reset a timer using the Reset() method. However, be careful when resetting timers, as it can lead to race conditions if not done properly.
  • Are tickers and timers precise?

    Tickers and timers are not guaranteed to be perfectly precise. The actual time at which an event is triggered may vary slightly due to system load and other factors.