Picture by Louis Hansel
Part I: The Bartender's Tale
A day at the local bar ...
πͺπΆ ............ A man walks into a bar.
π€΅π½π£π¨ ... The man asks the bartender for a seat at the bar.
β³ ................ The man waits for the bartender to make a seat available.
π€΅π½π£π¨ ... The bartender makes the seat available.
π₯π₯ .......... The man enjoys his drinks with some revelry.
π¨π½π£π€΅π½ ... When he is done, he says goodbye and leaves.
ππ½ ................ The bartender nods and says, "goodbye".
πͺπΆ ............ Another man walks into a bar ...
This is very unusual for a bar but allow yourself to suspend disbelief.
You see that it was a really small bar. A bar with just one seat. Somedays many people arrive simultaneously to claim the vacant seat at the bar. The bartender talks to all of them, letting them in one at a time. Now, this would make sense and it would work if the people have been queued up in the order of their time of arrival and if everybody respected the rules. Unfortunately, things were not that different at this bar. People broke queues.
ππ½ββοΈ ... πͺ . π€΅π½ . πͺ πΆπ½ββοΈπΆπ½ββοΈπΆπ½ββοΈπΆπ½ββοΈ
It would have been easier for the bartender to just announce the availability of the seat and to leave it to the crowd to decide who gets to drink next. The problem - there were always two or more people who claimed their right to the throne at the same time. It led to bar fights with deathlocks and people using the M-word. It made the bartender sad. All this violence and bad language were not good for business. It made things complicated.
Our bartender was a simple man. He wanted to stay out of trouble, and he knew that he couldn't control a mob. Nor could he rule over them justly. Instead, he invested in a device. He drilled a hole in the door.
Picture by CHUTTERSNAP
He put the below rules up outside the bar.
- πͺπΆ ............ Any man can walk up to the door.
- π¨π½π£π€΅π½ ... Any man in the bar can ask for a seat at the bar.
- β³ ................ Men must wait until the bartender makes a seat available.
- π€΅π½π£π¨π½ ... The men are allowed inside only if they have permission.*
- π₯π₯ ............ Men may take time to enjoy their drinks.
- π¨π½π£π€΅π½ ... Men must let it be known once they are done at the bar.
* It is declared hereof, that once a seat is available, the bartender will roll a handkerchief and push it out of the hole in the door to let one end hang outside. The first person outside the door who manages to grab on to that end, can pull it out and present it as permission to enter the bar.
ππ½ββοΈ ... πͺ . π€΅π½ β¬ πͺ πΆπ½ββοΈπΆπ½ββοΈπΆπ½ββοΈπΆπ½ββοΈ
This worked, well kind of. There were no fights inside the bar. It blocked the patrons and the kept the conflicts outside. Of course, it was not easy for all of the patrons. Some of them kept getting blocked by VIPs before they could reach the handkerchief. Some of them got tired, and had to take naps. This made them miss their opportunity several times. Their lives hadn't changed much. It was just a new set of rules. Their lives revolved around the bar door as if they were chained to it. M-words were spoken, but not aloud or out in the open.
The bartender's hardships increased in otherways. To keep his promise and to adhere to the rules, he found himself blocked at the door, even during off-hours. He had to stand there holding the handkerchief, even if there was no one outside. He couldn't do any other work until someone pulled the handkerchief away from him.
~ βͺ ~ breathe ~ βͺ ~
Part II: The Bar Conspiracy
And then there were gophers ...
Image by Lubos Houska
Late one night, the bartender was fast asleep holding his aching arm. He was awakened by the high pitched squeaks of some gophers that had sneaked into the bar. He mistook them for common rats and was about to smash them with a baton. But as he raised the stick, the gophers began to speak. They begged him for mercy. They assured him that they wouldn't ask much from him and will co-exist in harmony. They even promised him favors in returns, they were ready to become his slaves. They knew about his problems at the bar. The bartender dropped his stick and smiled as the gophers continued to detail out the agreement. There were similar high pitched squeaks heard all over the town. When the sun rose in the morning, the world was a little different.
The gophers had made a deal with all humans. They had promised to serve them by doing all their chores and all their work. Little did the humans know what they were getting into. The humans had fully accepted this unconditionally. The gophers didn't just do the menial and waiting jobs. They replaced humans everywhere, even at the bar. It was they who were doing the drinking, and them waiting at the door instead of the bartender. The poor bartender had retired to the attic, the gophers kept him busy. Coming to him in groups to let him know how things were going. Every time he got all his updates, he would usually tire and go back to bed.
It was the one thing the gophers couldn't do for the humans. They couldn't sleep on behalf of humans. So the humans, carried on living in the gopher world. Waiting for the gophers and sleeping. β¨
𦑠... πͺ . 𦑠⬠πͺ π¦‘π¦‘π¦‘π¦‘
~ βͺ ~ breathe ~ βͺ ~
Part III: The Bar Concurrency
We can translate the above tale into code using Go. But first ...
A light introduction to Go channels
Two ways to read from a channel, and one way to write into it
// read a value from a channelvalue <- someChannel
// read from a channel and ignore the value<- someChannel
// write a value into a channelsomeChannel <- "some value"
Go channels are just like the hole in the door
- If you are writing into the channel, you will be blocked until someone reads from it
- If you are reading from the channel, you will be blocked until someone writes into it
About goroutines
Go provides the go
keyword to execute functions in a separate goroutine.
name := "Norm"// will spawn a separate thread and unblock the main threadgo func() {fmt.Println(name)}
Goroutines can be thought about as separate threads
- They can be used to invoke inline functions too
- Go supports closures
Time to put it all together ...
// create a channelholeInTheDoor := make(chan string)// make availability known when the bar opens (don't block)go func () {// π€΅π½π£π¨π½π¨π½π¨π½holeInTheDoor <- "seating available"}()func (m Man) drinkAtTheBar() {go func() {// β³ wait until the seat is available<-m.holeInTheDoor// Do the actual drinking here πΊπ₯// π¨π½π£π€΅ announce done drinkingm.drinkingEnded <- "drinking ended"}go func () {// β³ wait until drinking has ended<-m.drinkingEnded// π€΅π½π£π¨π½π¨π½π¨π½ announce seating availableholeInTheDoor <- "seating available"}()}
Now we can let them all drink
for _, m := range men {m.drinkAtTheBar()}
Note:
drinkingEnded
is another channel that is used to communicate that drinking has ended. If a message is received on this channel, we can let people know about the availability of the seating.- We cannot predict who will be served first and when. We can be sure that the rules will be followed and that only one person will be allowed at the bar.
- The
for
loop above will terminate quickly since the functiondrinkAtTheBar
just spawns goroutines and returns. - We would need some mechanism for the parent thread to wait until the drinking is done for all patrons. This can be done using
sync.WaitGroup
, but we're going to leave it out to make the example shorter and focus on channels.
~ βͺ ~ look away into the distance ~ βͺ ~
Part IV: Raising the Bar
What if the bar expands the seating from 1 seat to 4 seats? Simple, we use a buffered channel of length 4 and write 4 messages into it initially.
- Buffered channels can hold up to 4 messages
- This means we can start with 4 messages, enabling 4 people to start drinking
- As and when the drinking sessions are done, new messages will be pushed into the channel
- Due to the capacity limit, there cannot be more than 4 messages in the channel buffer at any point in time
// create a buffered channel of length 4holeInTheDoor := make(chan string, 4)// make availability known when the bar opens (don't block)go func () {// π€΅π½π£π¨π½π¨π½π¨π½holeInTheDoor <- "seating available"holeInTheDoor <- "seating available"holeInTheDoor <- "seating available"holeInTheDoor <- "seating available"}()
Now let's say that the bartender feels bad about the lull at the bar between 4 to 5 pm, and he wants to increase the crowd at the door each day during this interval of time. Perhaps he can announce that there will be coupons distributed randomly between 4 to 5 pm. If someone is going to be present at the very instant that the coupons go out, they be able to claim the coupons. He doesn't want to block the gophers because he feels that he is already overworking them.
We can go with the select
clause and create a non-blocking channel write. If no one is waiting on the couponChannel
, the coupon will be lost.
// create a buffered channel of length 4couponChannel := make(chan string)// make availability known when the bar opens (don't block)go func () {// π€΅π½π£ πΈπΈselect {case couponChannel <- "You've won $50!":fmt.Println("coupon claimed!")default:fmt.Println("coupon lost!")}}()
And then there are those folks that are not too attached to the bar. They may not want to wait. They just want a quick peek and if there is no seat, they'd want to move on to other their priorities in life.
select
can be used for non-blocking channel reads as well in the below fashion. Here, if there is no message in holeInTheDoor
, the code will proceed to the default case.
func (m Man) tryDrinkAtTheBar() {select {case <-holeInTheDoor:fmt.Println("do the drink!")default:fmt.Println("imma outta here!")}}()
Hmm, so we touched upon the below in this article
- goroutines
- go channels
- unbuffered
- buffered
- operations on go channels
- blocking read
- non-blocking read
- blocking write
- non-blocking write
And with that, imma outta here too! Thanks for being with me through this. Take care of yourself!