Last week, I published an article on how to intuitively explain channels in Golang. If you're still confused about channels, check out that article first.
Go concurrency visual explanation - Channel
As a quick refresher: Partier, Candier and Stringer run a coffee shop. Partier is responsible for taking orders from customers and then passing these orders to the kitchen, where Candier and Stringer make coffee.
Gophers' Cafe
In this article, I will visually explain select
statements, another powerful tool for handling concurrency in Go applications. Gophers and their fictional cafe are still my companions, but this time, let’s focus on the party and the ordering part.
scene
Gopher's Cafe realized that more and more customers wanted to order coffee online through delivery apps. So, in addition to ordering in-store, they also opted for a delivery app. Partier monitors orders from both channels and queue
forwards these orders to Candier and Stringer through another channel called.
select {
case order := <-appOrders:
queue <- order
case order := <-inShopOrders:
queue <- order
}
When there is an order on either of the two channels, the Partier fetches the order and forwards it to queue
the channel.
If there are orders on both channels, one of them will be selected. In an actual coffee shop, inShopOrders
orders from . However, in a Go application we cannot guarantee which order will be selected. Also note that select
the execution of the statement will only select one order, and the Partier will not select two orders at a time. However, in many applications, select
statements are often nested within for
loops so that orders remaining in a previous iteration have a chance to be selected in the next iteration.
for {
select {
case order := <-appOrders:
queue <- order
case order := <-inShopOrders:
queue <- order
}
}
However, if both channels have orders, they will compete on a level playing field again.
Default
During non-peak hours, there are not many orders, and Partier spends a lot of time waiting. He figured he could use his time more efficiently by doing other things, such as clearing the table. This can default
be achieved by.
for {
select {
case order := <-appOrders:
log.Println("There is an order coming from appOrders channel")
queue <- order
case order := <-inShopOrders:
log.Println("There is an order coming from inShopOrders channel")
queue <- order
default:
log.Println("There is no order on both channels, I will do cleaning instead")
doCleaning()
}
}
time.After()
time.After(duration)
Typically select
used with to prevent permanent waits. Unlike default
, time.After(duration)
a normal time is created <-chan Time
, waits for duration
the elapse of time, and then sends the current time to the returned channel. This channel select
is treated equally with other channels in the statement. As you can see, select
the channels in the statement can be of different types.
shouldClose := false
closeHourCh := time.After(8 * time.Hour)
for !shouldClose {
select {
case order := <-appOrders:
log.Println("There is an order coming from appOrders channel")
queue <- order
case order := <-inShopOrders:
log.Println("There is an order coming from inShopOrders channel")
queue <- order
case now := <-closeHourCh:
log.Printf("It is %v now, the shop is closing\n", now)
shouldClose = true
default:
log.Println("There is no order on both channels, I will go cleaning instead")
doCleaning()
}
}
log.Println("Shop is closed, I'm going home now. Bye!")
This technique is very common when dealing with remote API calls because we have no guarantee when or if the remote server will return. With the aid of context
, this is usually not necessary.
responseChannel := make(chan interface{})
timer := time.NewTimer(timeout)
select {
case resp := <-