Home

Published

- 4 min read

Hello Concurrency

img of Hello Concurrency

Welcome to my very first blog post, and thank you for taking the time to read this, this means a lot to me.
For this post, let us begin with simple concurrency usage with Golang.

Background

In programming, we usually want to complete our tasks/jobs as fast as possible. Concurrency helps us achieve that by executing tasks/jobs concurrently instead of sequentially.

ELI5 Why we need it

Imagine trying to get 40 people up a bus. For a bus with 1 door, we might take a minute for everyone to board. However, if the bus has 2 doors we can shorten the time taken to 30 seconds.
In this case, the bus with 1 door has no concurrency while the bus with 2 doors has a concurrency of 2. INSERT IMAGE
\

How to use it

First, let us define a function to mimic a person boarding a bus:

// BoardBus mimics the time taken for a person to board a bus
func BoardBus() {
	time.Sleep(1*time.Second)
}

Now, we can test how long it takes 10 people to board a bus with only 1 door compared to a magic school bus with infinite doors.

  1. The bus with 1 door has zero concurrency.
  2. The magic school bus with infinite doors has unbounded conconcurrency.
// BoardBusWithoutConcurrency measures the time taken for 10 people to board without concurrency
func BoardBusWithoutConcurrency() {
  ts:= time.Now()
  for i:= 0; i < 10; i++ {
    BoardBus()
  }
  fmt.Println(time.Since(ts).Seconds())
}
// Output: 10.009233333

// BoardMagicSchoolBusWithConcurrency measures the time taken for 10 people to board with unbounded concurrency
func BoardMagicSchoolBusWithConcurrency() {
	ts := time.Now()
	wg := sync.WaitGroup{}
	
	for i:=0 ; i<10 ; i++ {
		wg.Add(1)
		go func ()  {
			BoardBus()
			wg.Done()
		}()
	}
	wg.Wait()
	fmt.Println(time.Since(ts).Seconds())
}
// Output: 1.0011585

As expected, the bus with infinite doors requires a much shorter time for all 10 people to board compared to the bus with only 1 door.

But hold on a minute, why does the code for the bus with infinite doors look so much more complicated?

Let’s list the key components here:

  1. go func(){}
  2. sync.WaitGroup{}

go func(){}()

The keyword here is go, go starts a separate goroutine to execute the function passed in(func (){}), although we used func(){} here, any other function works well here.

func main () {
  sentence := "Hello World!"
  // passing in a defined function
  go Shout(sentence)

  // passing in a function from library
  go fmt.Println(sentence)

  // passing in an inline function
  go func (input string)  {
		fmt.Println(input)
	}(sentence) // this becomes the input 
}

func Shout(sentence string) {
	fmt.Println(sentence)
}

sync.WaitGroup

WaitGroup lets us know when the goroutines are done, and we can continue with our main logic.

For example, say we send ten buses to ten different locations, how can we know for sure when they have reached their destination?

We can:

  1. If the trip takes 30 minutes, wait for an hour. (wait time >> estimated time required to guarantee bus has reached)
  2. Let them notify us when they have reached the location.

Both solution works but solution 2 is quicker and better.

WaitGroup helps us achieve solution 2 by allowing us to count the number of goroutines with wg.Add(1) and running goroutines to notify us when it is done via wg.Done().

Lastly, in the main routine, we will block at wg.Wait() until all goroutines have finished execution.

Rabbit Hole

In our examples, we called wg.Done() after we execute our functions and this is bad in some cases where the function for some reason returns early, and wg.Wait() will be waiting forever.

Let’s fix that:

func BoardMagicSchoolBusWithConcurrency() {
	ts := time.Now()
	wg := sync.WaitGroup{}
	
	for i:=0 ; i<10 ; i++ {
		wg.Add(1)
		go func ()  {
			defer wg.Done() // call wg.Done via defer instead
			BoardBus()
		}()
	}
	wg.Wait()
	fmt.Println(time.Since(ts).Seconds())
}

Curious about how defer works? I welcome you to dive deeper into this rabbit hole.

Alright, that is all for today. Let’s get one post smarter every day, and see you tomorrow! wg.Wait()

Subscribe

* indicates required

Intuit Mailchimp