Implement a Go Time-Based Scheduler
This challenge asks you to build a custom scheduler in Go that can execute functions at specific intervals or after a defined delay. This is a fundamental building block for many applications, including background jobs, periodic data fetching, and event-driven systems.
Problem Description
Your task is to create a Scheduler type in Go that allows users to schedule the execution of functions (func()). The scheduler should support two main types of scheduling:
- Delayed Execution: Execute a function once after a specified duration.
- Periodic Execution: Execute a function repeatedly at a fixed interval.
The Scheduler should be able to manage multiple scheduled tasks concurrently and ensure they are executed at the correct times without blocking the main application flow.
Key Requirements:
NewScheduler(): A constructor function to create a newSchedulerinstance.ScheduleOnce(delay time.Duration, task func()): A method to schedule a task for execution afterdelay.ScheduleInterval(interval time.Duration, task func()): A method to schedule a task for repeated execution everyinterval.Start(): A method to begin the scheduler's operation. This method should be non-blocking and allow the scheduler to run in the background.Stop(): A method to gracefully shut down the scheduler, canceling any pending or running tasks. This should allow any currently executing tasks to finish if possible (or have a mechanism to interrupt them, though finishing is preferred for this challenge).- Concurrency: The scheduler must handle multiple tasks concurrently.
- Reliability: Tasks should be executed as close to their scheduled time as possible, considering system load.
Expected Behavior:
- When
Start()is called, the scheduler begins monitoring scheduled tasks. - Tasks scheduled with
ScheduleOncewill execute after their specified delay. - Tasks scheduled with
ScheduleIntervalwill execute repeatedly with the given interval. - When
Stop()is called, the scheduler should cease scheduling new tasks and attempt to clean up existing ones.
Edge Cases to Consider:
- Scheduling a task with a zero or negative duration/interval.
- Calling
Start()multiple times. - Calling
Stop()beforeStart()or multiple times. - Tasks that take a very long time to execute.
- A large number of scheduled tasks.
Examples
Example 1: Delayed Execution
package main
import (
"fmt"
"time"
)
func main() {
scheduler := NewScheduler()
go scheduler.Start() // Run scheduler in a goroutine
task1 := func() {
fmt.Println("Task 1 executed after 2 seconds.")
}
scheduler.ScheduleOnce(2*time.Second, task1)
time.Sleep(3 * time.Second) // Allow time for task to execute
scheduler.Stop()
fmt.Println("Scheduler stopped.")
}
Expected Output (approximately):
Task 1 executed after 2 seconds.
Scheduler stopped.
Example 2: Periodic Execution
package main
import (
"fmt"
"time"
)
func main() {
scheduler := NewScheduler()
go scheduler.Start() // Run scheduler in a goroutine
counter := 0
task2 := func() {
counter++
fmt.Printf("Task 2 executed (count: %d) every 1 second.\n", counter)
}
scheduler.ScheduleInterval(1*time.Second, task2)
time.Sleep(3500 * time.Millisecond) // Let it run for ~3.5 seconds
fmt.Println("Stopping scheduler...")
scheduler.Stop()
fmt.Println("Scheduler stopped.")
}
Expected Output (approximately):
Task 2 executed (count: 1) every 1 second.
Task 2 executed (count: 2) every 1 second.
Task 2 executed (count: 3) every 1 second.
Stopping scheduler...
Scheduler stopped.
Example 3: Mixed Scheduling and Stopping
package main
import (
"fmt"
"time"
)
func main() {
scheduler := NewScheduler()
go scheduler.Start()
taskA := func() { fmt.Println("Task A (once) executed.") }
taskB := func() { fmt.Println("Task B (interval) executed.") }
taskC := func() { fmt.Println("Task C (once, delayed) executed.") }
scheduler.ScheduleOnce(500*time.Millisecond, taskA)
scheduler.ScheduleInterval(700*time.Millisecond, taskB)
scheduler.ScheduleOnce(2*time.Second, taskC)
time.Sleep(1 * time.Second)
fmt.Println("Stopping scheduler...")
scheduler.Stop()
fmt.Println("Scheduler stopped.")
}
Expected Output (approximately):
Task A (once) executed.
Task B (interval) executed.
Stopping scheduler...
Scheduler stopped.
(Note: Task C might not execute as the scheduler stops before its scheduled time).
Constraints
- The scheduler must be implemented using standard Go libraries. No external third-party scheduling libraries are allowed.
- The
Schedulertype should be thread-safe. Multiple goroutines can callScheduleOnce,ScheduleInterval,Start, andStop. - Tasks scheduled with
ScheduleIntervalshould execute at mostN+1times ifStop()is called afterNexecutions have completed. - The
Stop()method should signal to all active goroutines responsible for executing tasks that they should exit.
Notes
- Consider using
time.Timerandtime.Tickerfor managing the timing of your tasks. - A central loop within the
Start()method will likely be necessary to manage scheduled jobs. - Using channels can be very effective for communication between different parts of your scheduler (e.g., signaling tasks to stop).
- Think about how to represent scheduled tasks internally. A struct containing the task function, its schedule type, and timing information would be appropriate.
- For
ScheduleInterval, ensure that the interval between executions is consistent, even if a task takes longer than the interval to complete. A common approach is to usetime.Tickerfor this.