Channel Synchronization in Golang: [4 Best Examples]

In this tutorial, we will learn about Channel synchronization in Golang using 4 best examples.  Channel in Golang provide a safe way for Goroutines to share data, avoid race conditions and ensuring data integrity. Channel can enforce a sequence of operations by making Goroutines wait for the appropriate signal or data before proceeding.

 

Channel Synchronization in Golang: [4 Best Examples]

What is Channel Synchronization?

In Golang, Channel synchronization is powerful concept that allows different Goroutines to communicate and coordinate their actions in synchronized manner. It let Goroutines to send signal to each other via channel ensuring that certain operations happen in a specific order or only when conditions are met. Let’s understand this with a real life example.

When we go to a restaurant and order some food, Waiter and Chef has to do the coordination in order to proceed and deliver the food on our table. So here Waiter and Chef can be think of two different Goroutine that does channel synchronization and both wait for each other’s action before proceeding.

 

Channel Synchronization Features

There are broadly two features of Channel synchronization. They are:
Blocking –  When a Goroutine sends data on a channel, it will block until there is a receiver ready to receive that data. Similarly, when a Goroutine tries to receive data from a channel, it will block until there is a sender ready to send.

Ordering –  Channels can be used to enforce a specific order of execution between different Goroutines. For example, a Goroutine might wait for data on one channel before sending data on another cannel.

 

Channel Synchronization in Golang: [4 Best Examples]

Also read: Concurrency in Golang with Best Examples

Now that we understand what Channel synchronization is and its features, let’s look at some of the examples to implement the concept. There is one similarity in below 4 examples and i.e the processing time taken to execute the Goroutines. If you do not have prior knowledge of what Goroutines are, please make yourself familiar by referring to How to create Goroutine in Golang with Examples. Notice that even though the approach if different in each example, the processing time taken by each example is approximately 2s.  

Example-1: Use two goroutine to calculate cube root 

In this example, we will calculate the cube root of a number and print the report once the calculation is done. To do so, we have created two functions, each converted into Goroutine. calCubeRoot function will calculate the cube root and printReport function will print the result of cube root.

package main

import (

    "fmt"
    "time"
)

var num = 88
var result = 0

func main() {

    cuberootChan := make(chan int)
    reportChan := make(chan string)
    startTimer := time.Now()
    go calCubeRoot(num, cuberootChan)
    go printReport(cuberootChan, reportChan)
    <-reportChan
    stopTimer := time.Since(startTimer)
    fmt.Println("\nProcess took", stopTimer, "to complete")
}

func calCubeRoot(num int, cuberootChan chan int) {

    fmt.Println("Taking 2 seconds to calculate cube root.....")
    time.Sleep(time.Second * 2)
    result = num * num * num
    cuberootChan <- result
}

func printReport(cuberootChan chan int, reportChan chan string) {

    time.Sleep(time.Second * 1)
    cubeRootResult := <-cuberootChan
    fmt.Println("Result of cube root of", num, "is:", cubeRootResult)
    reportChan <- "Report generated"
}

OUTPUT
PS C:\User\linuxnasa\OneDrive\Desktop\Go-Dump> go run .\hello-world.go
Taking 2 seconds to calculate cube root.....
Result of cube root of 88 is: 681472

Process took 2.0106997s to complete

 

Example-2: Use nested functions to calculate cube root

In this example, instead of using two separate functions, we now will create two nested functions inside main() function and do the same operation which we did in example-1.

package main

import (

    "fmt"
    "time"
)

var num = 88
var result = 0

func main() {

    cuberootChan := make(chan int)
    reportChan := make(chan string)
    startTimer := time.Now()
    calCubeRoot := func() {                        //nested function

        fmt.Println("Taking 2 seconds to calculate cube root.....")
        time.Sleep(time.Second * 2)
        result = num * num * num
        cuberootChan <- result
    }

    printReport := func() {                        //nested function

        time.Sleep(time.Second * 1)
        cubeRootResult := <-cuberootChan
        fmt.Println("Result of cube root of", num, "is:", cubeRootResult)
        reportChan <- "Report generated"           
    }

    go calCubeRoot()
    go printReport()
    <-reportChan // Wait for the report to be generated
    stopTimer := time.Since(startTimer)
    fmt.Println("\nProcess took", stopTimer, "to complete")

}

OUTPUT
PS C:\Users\linuxnasa\OneDrive\Desktop\Go-Dump> go run .\hello-world.go
Taking 2 seconds to calculate cube root.....
Result of cube root of 88 is: 681472

Process took 2.0037897s to complete

 

Example-3: Use one goroutine to calculate cube root

In this example, we will create one large Goroutine inside which entire process is put and executed.

package main

import (

    "fmt"
    "sync"
    "time"
)

func main() {

    var wg sync.WaitGroup
    wg.Add(1)
    go func() {

        defer wg.Done()     // Mark the wait group as done when the goroutine exits
        var num = 88
        cuberootChan := make(chan int)
        reportChan := make(chan string)
        startTimer := time.Now()
        calCubeRoot := func() {

            fmt.Println("Taking 2 seconds to calculate cube root.....")
            time.Sleep(time.Second * 2)
            cubeRootResult := num * num * num       // Calculate result directly
            cuberootChan <- cubeRootResult
        }

        printReport := func() {

            time.Sleep(time.Second * 1)
            cubeRootResult := <-cuberootChan
            fmt.Println("Result of cube root of", num, "is:", cubeRootResult)
            reportChan <- "Report generated"
        }

        go calCubeRoot()
        go printReport()
        <-reportChan          // Wait for the report to be generated
        stopTimer := time.Since(startTimer)
        fmt.Println("\nProcess took", stopTimer, "to complete")
    }()

    wg.Wait()                  // Wait for the wait group to be done
}

OUTPUT
PS C:\Users\linuxnasa\OneDrive\Desktop\Go-Dump> go run .\hello-world.go
Taking 2 seconds to calculate cube root.....
Result of cube root of 88 is: 681472

Process took 2.0090586s to complete

 

Example-4: Use for loop to calculate cube root

In this example, We are using a for loop to run 1000 Goroutines concurrently. That means cube root now will be calculated for each number and printer till 1000 unlike how we saw in above examples where we were calculating the cube root for a specific number.

package main

import (

    "fmt"
    "sync"
    "time"
)

func main() {

    startTimer := time.Now()
    var wg sync.WaitGroup

    for i := 1; i <= 1000; i++ {
        wg.Add(1)
        go func(j int) {

            defer wg.Done()                // Mark the wait group as done when the goroutine exits
            cuberootChan := make(chan int)
            reportChan := make(chan string)
            calCubeRoot := func() {

                time.Sleep(time.Second * 2)
                result := j * j * j        // Calculate result directly
                cuberootChan <- result
            }

            printReport := func() {

                time.Sleep(time.Second * 1)
                cubeRootResult := <-cuberootChan
                fmt.Println("Result of cube root of", j, "is:", cubeRootResult)
                reportChan <- "Report generated"
            }

            go calCubeRoot()
            go printReport()
            <-reportChan                  // Wait for the report to be generated
        }(i)                              // Pass i as a parameter to the goroutine

    }

    wg.Wait()                             // Wait for all goroutines to be done
    stopTimer := time.Since(startTimer)
    fmt.Println("\nProcess took", stopTimer, "to complete")
}
OUTPUT
PS C:\Users\linuxnasa\OneDrive\Desktop\Go-Dump> go run .\hello-world.go
Result of cube root of 961 is: 887503681
Result of cube root of 979 is: 938313739
Result of cube root of 524 is: 143877824
Result of cube root of 67 is: 300763
Result of cube root of 68 is: 314432
Result of cube root of 14 is: 2744
.....................................
.....................................
.....................................
Result of cube root of 578 is: 193100552
Result of cube root of 52 is: 140608

Process took 2.1532056s to complete

 

Summary

Channel synchronization is a versatile tool for designing concurrent programs that operate harmoniously. By properly structuring goroutines and using channels, developers can effectively manage the flow of information and synchronization between parallel tasks, resulting in efficient and reliable concurrent applications.

Leave a Comment