Implement a Custom Ticker in Go
This challenge requires you to implement a simplified version of Go's built-in time.Ticker. You will create a struct that, when a method is called, will repeatedly send a value on a channel at a specified interval. This is a fundamental pattern for scheduling recurring tasks in Go applications.
Problem Description
Your task is to create a Go struct, let's call it CustomTicker, that mimics the behavior of time.Ticker. This CustomTicker should have a method that, when invoked, starts a goroutine to send a signal (an empty struct struct{}) on a provided channel at regular, configurable intervals.
Key Requirements:
NewCustomTicker(interval time.Duration)function: This function should return a pointer to aCustomTickerinstance. It will take the desired interval between ticks as an argument.Start(c chan<- struct{})method: This method, called on aCustomTickerinstance, should:- Start a new goroutine.
- Inside the goroutine, continuously wait for the specified
intervaland then send an empty struct (struct{}) onto the provided channelc. - The goroutine should continue to run as long as the
CustomTickeris active.
Stop()method: This method should gracefully stop the ticker's goroutine, preventing further signals from being sent on the channel.- Channel Usage: The
CustomTickershould not create its own channel but rather send signals on a channel provided by the caller to theStartmethod.
Expected Behavior:
When Start is called, the provided channel c will begin receiving signals every interval duration. Calling Stop will halt these signals.
Edge Cases:
- What happens if
Stopis called beforeStart? - What happens if
Startis called multiple times on the sameCustomTickerinstance without callingStopin between? - What happens if the caller stops reading from the channel
c? (While not strictly your responsibility to handle as the ticker, it's good to be aware of potential blocking scenarios).
Examples
Example 1:
package main
import (
"fmt"
"time"
)
// Assume CustomTicker is implemented as per the problem description.
func main() {
ticker := NewCustomTicker(500 * time.Millisecond) // Tick every 500ms
tickChan := make(chan struct{})
go func() {
ticker.Start(tickChan)
fmt.Println("Ticker started.")
}()
// Simulate receiving ticks for a short period
for i := 0; i < 5; i++ {
<-tickChan // Wait for a tick
fmt.Printf("Tick received at: %s\n", time.Now().Format("15:04:05.000"))
}
ticker.Stop()
fmt.Println("Ticker stopped.")
// Give a moment to ensure no more ticks are sent
time.Sleep(600 * time.Millisecond)
}
Expected Output (timing will vary slightly):
Ticker started.
Tick received at: 10:30:00.500
Tick received at: 10:30:01.000
Tick received at: 10:30:01.500
Tick received at: 10:30:02.000
Tick received at: 10:30:02.500
Ticker stopped.
Explanation: The CustomTicker is initialized to tick every 500 milliseconds. It's started, and the main goroutine then waits for 5 ticks, printing a timestamp for each. After receiving 5 ticks, Stop is called, and the program exits.
Example 2:
package main
import (
"fmt"
"time"
)
// Assume CustomTicker is implemented as per the problem description.
func main() {
ticker := NewCustomTicker(1 * time.Second) // Tick every 1 second
tickChan := make(chan struct{})
go func() {
ticker.Start(tickChan)
fmt.Println("Ticker started.")
}()
// Stop immediately after starting
time.Sleep(100 * time.Millisecond) // Small delay to ensure goroutine starts
ticker.Stop()
fmt.Println("Ticker stopped immediately.")
// Wait to see if any ticks are sent (they should not be)
select {
case <-tickChan:
fmt.Println("Unexpected tick received!")
case <-time.After(1 * time.Second):
fmt.Println("No unexpected ticks received.")
}
}
Expected Output:
Ticker started.
Ticker stopped immediately.
No unexpected ticks received.
Explanation: The ticker is started, but Stop is called very shortly after. The select statement demonstrates that no ticks are received within the subsequent second, confirming that Stop worked correctly.
Constraints
- The
intervalprovided toNewCustomTickerwill be a positivetime.Duration(e.g.,time.Second,100 * time.Millisecond). - The
chan<- struct{}passed toStartwill be a valid, non-nil channel. - The implementation should be efficient and avoid busy-waiting.
Notes
- Consider how to signal the goroutine to terminate gracefully when
Stop()is called. Channels are excellent for this in Go. - Think about potential race conditions if
StartorStopare called concurrently. For this challenge, assumeStartandStopwill not be called concurrently on the sameCustomTickerinstance. - The
Stop()method should ideally prevent new ticks from being sent after it's called, but it doesn't need to cancel a tick that is already in progress of being sent (though a robust implementation might aim for this).