• about reply
Alpha Reply Logo
Menu
  • Solutions
    Solutions
    • Solutions
    • Fusion
    • Synapse
  • Insights
    Insights
    • Insights
    • Success Stories
    • Articles
    • Events
    • Tutorials
  • Partnerships
Choose language:
  • about Reply
Alpha Reply Logo
Focus On

Go Concurrency with Channels

goconcurrencywithchannels-1408x704.jpg 0

Welcome back to the series tutorials on Go Concurrency. If you missed the previous tutorials please refer to: Go Concurrency with GoRoutines.

In this tutorial, we will help you understand the concept of Channels.

What is Channel

With concurrency programming, communication in a memory sharing environment plays a vital role in synchronisation across your programme. In Go, Channels area built-in feature for synchronisation purposes. They mainly act as a data transfer pipeline that GoRoutines can either send to or read from. Hence, each GoRoutine (including main() has access to the same shared memory and this becomes particularly useful when there are multiple GoRoutines. Let's dive into a simple example.

package main
 
import (
  "fmt"
)
 
func main() {
    c := make(chan string)
 
    go countCat(c)
 
    for i := 0; i < 5; i++ {
        message := <- c
        fmt.Println(message)
    }
}
 
func countCat(c chan string) {
    for i := 0; i < 5; i++ {
        c <- "Cat"
    }
}

Firstly, we create a new channel c with make() where we assigned "string" as the data type which c receives from. Note that a channel can only be assigned one data type. The GoRoutine gocountCat() sends "Cat" into c with the syntax c<-"Cat" whereas message receives from c with the syntax <-c in the main GoRoutine, followed by printing the variable. In this example, countCat() is the sender whereas main() is the receiver/consumer. We are sending 5 strings of "Cat" into the channel and subsequently printing all 5 "Cat" in main(). Channel operation (i.e. write or read) are blocking in nature.

This means:

  • When we send data into the channel using a GoRoutine, it will be blocked until the data is consumed by another GoRoutine.
  • When we receive data from channel using a GoRoutine, it will be blocked until the data is available in the channel.

Deadlock errors occur when all GoRoutines are all blocked or "sleeping".

Let's make a small change to the code and discuss in more detail about Channels.

package main

import (
   "fmt"
)

func main() {
   c := make(chan string)
   go countCat(c)

   for i := 0; i < 4; i++ {
      message := <- c
      fmt.Println(message)
   }
}

func countCat(c chan string) {
   for i := 0; i < 5; i++ {
      c <- "Cat"
   }
}

This time the main function only consumes part of data in the Channel (i.e. the for loop in main() only has 4 steps whereas the for loop in countCat() has 5 steps). Can you spot the potential problems in this script?

Yes, you are right, the countCat GoRoutine actually has been blocked forever because the last "Cat" sent into the channel is not consumed by any other GoRoutines. The reason we do not see deadlock here is because the main() GoRoutine exits immediately after printing four "Cat". Let's make this example more clear with the help of WaitGroup.

package main

import (
   "fmt"
   "sync"
)

func main() {
   c := make(chan string)
   wg := sync.WaitGroup{}
   wg.Add(1)
   go countCat(c, &wg)

   for i := 0; i < 4; i++ {
      message := <- c
      fmt.Println(message)
   }
   wg.Wait()
}

func countCat(c chan string, wg *sync.WaitGroup) {
   for i := 0; i < 5; i++ {
      c <- "Cat"
   }
   wg.Done()
}

This time, you will face a deadlock because the main() GoRoutine is asked to wait until the go countCat is done. However, countCat is never done as we explained earlier due to the fact that Channel operations are blocking the GoRoutines. More specifically, the last GoRoutine of countCat() at the 5th step (i.e. c <-"Cat") was blocked and not consumed by main () GoRoutine since it stopped at the 4th step. Hence, wg.Done() in countCat() was never executed.

Alternatively, we can make the main() to be the sender and other GoRoutines to be the receiver.consumer (see example below). In this case, you must put the receiver GoRoutines before you send data in main(), otherwise deadlock will happen.

package main

import (
   "fmt"
)

func main() {
   c := make(chan string)
   go countCat(c)

   for i := 0; i < 5; i++ {
      c <- "Car"
   }
}

func countCat(c chan string) {
   for i := 0; i < 5; i++ {
      message := <- c
      fmt.Println(message)
   }
   fmt.Println("done")
}

Buffered and Unbuffered Channels

Next, we have a simple example that is a common mistake often made by beginners. Let's have a look at it.

package main

import "fmt"

func main(){
   c := make(chan string)

   c <- "Cat"
   message := <- c
   fmt.Println(message)
}

This code will produce a deadlock because it tries to send and receive data from Channel C within the same GoRoutine which is main() in this case. Since Channel operations are blocking the GoRoutine, the main() GoRoutine stops at c<-"Cat" forever since it's blocked and it would never reach the line message:= <-c. The simple workaround for this problem is to use a buffered channel. The buffered channel will not block the GoRoutine unless it is full.

package main

import "fmt"

func main(){
   c := make(chan string, 1)

   c <- "Cat"
   message := <- c

   fmt.Println(message)

}

In this example, we make a buffered channel with the size equal to 1. In this case, the channel can hold one data and will not block main() so that we can proceed to the line message:= <-c. However, if we try to spend more than one data to the channel before we receive data from it, deadlock will happen again because the size of this buffered channel is only 1 (see example below).

package main

import "fmt"

func main(){
   c := make(chan string, 1)

   c <- "Cat"
   c <- "Cat"
   message := <- c

   fmt.Println(message)

}

Closing Channels

The last part of this tutorial is about closing channels and using range to loop through channels.

package main

import (
   "fmt"
   "sync"
)

func main() {
   c := make(chan string)
   wg := sync.WaitGroup{}
   wg.Add(1)
   go countCat(c, &wg)

   for message:= range c {
      fmt.Println(message)
   }
   wg.Wait()
}

func countCat(c chan string, wg *sync.WaitGroup) {
   for i := 0; i < 5; i++ {
      c <- "Cat"
   }
   wg.Done()
   close(c)
}

In this example, we use close(c) to close the channel in countCat after we finish sending all data to the channel. In other words, we are telling the channel that there will be no more data sent into this channel. In main(), we use range instead of a specific number of iterations to receive data from the channel. The range syntax will detect if the channel has been closed or not. Otherwise, it will produce a deadlock if we do not close the channel in the sender GoRoutine while using range.

Next week, we are going to talk about one of the most critical elements of Concurrency Programming which is Mutex. The concept of Mutex exists widely in computer programming and we are going to look at it from the GoLang perspective.

Stay tuned!

RELATED CONTENTS

Go concurrency with Mutex 0

Go concurrency with Mutex

Mutex is one of the most critical concepts in concurrent programming. When you use GoRoutine and Channels to perform concurrent programming, have you ever thought about what happens when two GoRoutines try to access the same piece of data in the same memory location? ...

Go Concurrency with WaitGroup 0

Go Concurrency with WaitGroup

In this tutorial, we will help you to understand the concept of WaitGroup. In some scenarios, you may need to block certain parts of code to allow these GoRoutines to complete their execution according to your needs. A common usage of WaitGroup is to...

go concurrency with goroutines

Golang has become a popular language due to the features that allow developers to build incredibly fast applications using the power of concurrency programming. In this series of tutorials, we will learn about...

go concurrency with goroutines 0
 
 
 
 
Reply ©​​ 2023​ - Company Information -
 PrivacyCookie Settings​​
  • About Reply​
  • Inves​tors​​
  • Newsroom
  • Follow us on
  • ​​
​​
  • ​
  • ​Privacy & Cookies Policy​
  • Information (Client)
  • Information (Supplier)
  • Information (Candidate)​​