Concurrency in Golang with Best Examples

In this tutorial, We will learn about Concurrency in Golang with Best Examples. In programming world, concurrency can really be think of as decomposing a program into multiple parts and each part can run independently with each other. Traditional method of implementing the concurrency is by using the threads but they have complexities and challenges. So Golang implemented Goroutines   using which concurrency can be achieved without all those complexities which one sees while using threads. We will learn about some important concept of concurrency and then we will implement concurrency using an example.

 

Concurrency in Golang with Best Examples

Understanding Concurrency in Golang

Also read: How To Install Go Extension in VS Code Using 9 Easy Steps

Concurrency in Golang is a mechanism to do multiple tasks at once and this is achieved by goroutines. If you do not know what Goroutines are, I will suggest you grab some prior high level knowledge on  Goroutine for better understanding of Concurrency. Concurrency must not be think of same as parallelism. These two are completely different term even when they sound like they do the same job.
Concurrency means dealing with lot of tasks at once whereas Parallelism means doing lot of tasks at once. For example, parallelism is when 4 people are running parallelly without any order whereas concurrency is when each person is waiting for next person to  complete certain distance before start running i.e all 4 will run but in order. Now that we understand Concurrency, let’s look at another important concept called Blocking Code in next section before jumping into the example.

 

Understanding Blocking Code in Golang

Blocking usually means waiting for something. Blocking code does not allow its process to continue until blocking call finishes execution. Blocking code allows execution to continue during waiting periods. So, basically blocking code is like  normal code in that it must complete before the process can continue. What makes it unique in context of concurrency is that “completion” generally depends on status of other goroutines. Some goroutines can continue while others are blocked. In concurrency, blocking code is  used in couple of specific way to do the jobs:

  • It forces main() function to wait for other goroutines to complete and that is generally done by waitgroup. waitgroup is a counter which blocks the execution of goroutines until it’s internal counters become zero.
  • Synchronize goroutines at very specific points – for example, to exchange variables through channels. channels can be think of a medium which connects concurrent goroutines. This means goroutines can send and receives values using channels.
  • Prevent two goroutines from accessing a shared variable at the same time. This can be done using mutexes, condition variables, atomic variables.

 

Concurrency in Golang with Best Examples

Now let’s look at an example to implement concurrency using waitgroup. The syntax used to declare waitgroup is:

var wg_counter = sync.Waitgroup{}

Where,
wg_counter is a variable name
sync is the package imported
Waitgroup{} is the function used from sync package.

wg_counter  is a counter which will store the number of outstanding  Goroutines that  that we want to wait for. Now see the below code.

 

package main

import (

    "fmt"
    "sync"                                                  //import sync package
    "time"
)
var wg_counter = sync.WaitGroup{}                           //declare  waitgroup counter

func main() {

    wg_counter.Add(2)                                        //add 2 goroutines
    start_timer := time.Now()
    go fourWheeler()
    go twoWheeler()
    wg_counter.Wait()                                         //wait until both two goroutine finish
    fmt.Println("\nMain function execution is completed")
    stop_timer := time.Since(start_timer)
    fmt.Println("\nProcess took", stop_timer, "to complete")

}


func fourWheeler() {

    time.Sleep(time.Second * 2)
    fmt.Println("\nHi from fourWheeler function")
    wg_counter.Done()                                    //decrement the wait counter
}

func twoWheeler() {

    time.Sleep(time.Second * 2)
    fmt.Println("\nHi from twoWheeler function")
    wg_counter.Done()                                    //decrement the wait counter
}
OUTPUT
PS C:\Users\linuxnasa\OneDrive\Desktop\Go-Dump> go run .\hello-world.go

Hi from fourWheeler function
Hi from twoWheeler function
Main function execution is completed
Process took 2.000811s to complete

In above example, We have declared a waitgroup counter. In main function, we have added 2 Goroutines using wg_counter.Add(2) . We then called the first goroutine fourWheeler(). Once  it completes it’s execution, it will call wg_counter.Done() method  and decrease the wg_counter by 1 . Next, second Goroutine twoWheeler() is called.  It will also call wg_counter.Done() method to decrease the wg_counter. Once wg_counter becomes o, it will execute rest of the code in main function. If you look at the processing time taken by complete execution is 2.xx sec which is less than the normal functions.

 

Conclusion

We saw how concurrency can be achieved using waitgroup counters. If you notice, waitgroup provides only limited information about the goroutines. For example,  it says how many number of Goroutines are operational and each Goroutine itself has to say when they are finished using Done() method. But what if we require more information about the Goroutines while they are operational? . We can use channels in this case. To learn about Channels in Golang, please refer to Channel in Golang: [5 Best Examples]

Leave a Comment