Dwi Wahyudi
Senior Software Engineer (Ruby, Golang, Java)
In this post we will write some code using Golang’s timers, an utility to execute code (at some point) in the future.
Overview
A timer would be useful for us, if we want to wait for certain times, so when the timer is up, a certain code will run. Basically a timer will just wait and block code execution for specified duration.
Please note that a timer will only trigger once, if we want to do repeated tasks (with certain interval), we can use tickers: Golang Tickers.
Imagine that we’re working as a manager in a restaurant, we know that foods and beverages are cooked (and prepared) at different durations.
func menuCookTime() map[string]int{
menuCookTime := map[string]int{
"bakso":3,
"burger":4,
"coffee":1,
"tea":1,
"pizza":6,
}
return menuCookTime
}
For example, as we can see above menu, pizza takes the longest time to cook and prepare. Assume numbers above as number of seconds.
For each completed menu, we will notify with this function:
func receiveCookTime(cookTime *time.Timer, menu string) {
<-cookTime.C
fmt.Println("Cook Time completed: " + menu)
}
Now, let’s demonstrate a completed order for a burger:
func demoSingleTimer() {
menu := "burger"
burgerCookTime := menuCookTime()[menu]
cookTime := time.NewTimer(time.Duration(burgerCookTime) * time.Second)
receiveCookTime(cookTime, menu)
}
Be aware of timer and its channel.
- Wait, and send, we use
time.NewTimer()
function to create a new timer instance, we must specify it with certain duration, this indicates that after such duration there will be data sent tocookTime.C
channel. - Receive, the timer instance is then passed to
receiveCookTime
function, where it waits for specified duration with<-cookTime.C
channel, when thatcookTime
timer run-out, thencookTime.C
channel will receive.
Code execution: “hey, i want to continue the code execution” cookTime.C: “wait, specified waiting time is 5 seconds” Code execution: “I want to continue now…" cookTime.C: “wait.. 2 more seconds”… cookTime.C: “half second left, and… alright… you can continue code execution now.”
When we run the demoSingleTimer()
function, after 3 seconds, Cook time completed for burger will appear. Burger menu has 3 value in menu above, we specified it as param to time.NewTimer
, burger cook time as cookTime
in receiveCookTime
function, will wait and block code execution for 3 seconds.
Finally we got to the lunch hour, and there is a customer who orders all of the menu.
func demoMultipleTimers() {
for menu, cookTime := range menuCookTime() {
cookTime := time.NewTimer(time.Duration(cookTime) * time.Second)
receiveCookTime(cookTime, menu)
}
}
Surely enough, we can notice that timers is indeed a blocking operation, if we call receiveCookTime
in main goroutine, 5 menus means 5 instances of timers, each of it will wait until each instance of cookTime
timer got run-out. So the first menu (bakso) will wait on its timer for 3 seconds, before it goes to another (next iteration in for loop), and so on. demoMultipleTimers()
function will run each cook time in sequence for a total of 15 seconds (all of menu durations).
Timers and Async Operations
We as the manager of the restaurant, notice this, and decide that we should add more cooking staffs, so they can prepare customers’ orders in concurrent. Concurrent processing means that our timers will run simultaneously as well. No worry, we can just send the receiveCookTime
to other goroutines.
func demoMultipleTimersAsync() {
for menu, cookTime := range menuCookTime() {
cookTime := time.NewTimer(time.Duration(cookTime) * time.Second)
go receiveCookTime(cookTime, menu)
}
}
By sending receiveCookTime
to other goroutines, we can now have 5 instances of timers at a time.
When we execute demoMultipleTimersAsync
, only a total of 6 seconds needed for all timers to complete. Coffee and tea will appear at the same time, while pizza will be the last (after 6 seconds).
Stopping The Timers
We can stop the timers if we want, every timer instance have Stop()
method that we can call, once stopped, timer won’t trigger.
func stopTimer(cookTime *time.Timer, menu string) {
time.Sleep(2 * time.Second)
fmt.Println("Timer stop for:", menu)
cookTime.Stop()
}
In above function, we receive a timer instance, after 2 seconds, the timer will be stopped. Let’s use this function in our code:
func demoMultipleTimersAsyncWithStop() {
for menu, cookTime := range menuCookTime() {
cookTime := time.NewTimer(time.Duration(cookTime) * time.Second)
go stopTimer(cookTime, menu)
go receiveCookTime(cookTime, menu)
}
}
Resetting The Timers
We can also reset a timer instance, means that it will reset the time needed for the timer instance to trigger. It will return true, if timer is still running, otherwise it will return false if it’s already stopped or triggered.
func resetTimer(cookTime *time.Timer, menu string) {
fmt.Println("Timer reset for:", menu)
cookTime.Reset(4 * time.Second)
}
Above function will immediately reset the timer to 4 seconds. Let’s apply this to our code:
func demoMultipleTimersAsyncWithReset() {
for menu, cookTime := range menuCookTime() {
cookTime := time.NewTimer(time.Duration(cookTime) * time.Second)
go resetTimer(cookTime, menu)
go receiveCookTime(cookTime, menu)
}
}
When we run this code, all of menu timers will reset to 4 seconds. All of menu order completion will trigger after 4 seconds.
Here’s the code of the demonstration: https://github.com/dwahyudi/golang-timers