The Bar Concurrency 🍺πŸ₯‚

December 3, 2020β˜• 12 min read


a lonely bar
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.


a hole in the door
Picture by CHUTTERSNAP

He put the below rules up outside the bar.

  1. πŸšͺ🚢 ............ Any man can walk up to the door.
  2. πŸ‘¨πŸ½πŸ“£πŸ€΅πŸ½ ... Any man in the bar can ask for a seat at the bar.
  3. ⏳ ................ Men must wait until the bartender makes a seat available.
  4. πŸ€΅πŸ½πŸ“£πŸ‘¨πŸ½ ... The men are allowed inside only if they have permission.*
  5. πŸ₯ƒπŸ₯‚ ............ Men may take time to enjoy their drinks.
  6. πŸ‘¨πŸ½πŸ“£πŸ€΅πŸ½ ... 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 ...


gopher
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 channel
value <- someChannel
// read from a channel and ignore the value
<- someChannel
// write a value into a channel
someChannel <- "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 thread
go 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 channel
holeInTheDoor := 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 drinking
m.drinkingEnded <- "drinking ended"
}
go func () {
// ⏳ wait until drinking has ended
<-m.drinkingEnded
// πŸ€΅πŸ½πŸ“£πŸ‘¨πŸ½πŸ‘¨πŸ½πŸ‘¨πŸ½ announce seating available
holeInTheDoor <- "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 function drinkAtTheBar 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 4
holeInTheDoor := 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 4
couponChannel := 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!