Concurrency in Golang [Part-3] (Channels)

Photo by Chinmay B on Unsplash

Concurrency in Golang [Part-3] (Channels)

In the previous post on this topic, we discuss sync package inside which we discussed waitGroup and mutex. Here in this post, we are going to discuss Channels in Golang.

Why do we need channels?

we have already discussed the challenges with concurrency.

  • Coordinating tasks

    • This one is addressed by waitGroup
  • Shared memory

    • This one is addressed by Mutexes

So where do we need the channel? Well, channels can help in addressing both issues. Let's see how

Channels

We have two goroutines and for some reason, they want to communicate with each other. Here Mutexes can help us with communication but the same thing we can achieve with channels let's see the operations involved in channels.

Creating channels

// create a unbuffered channel 
ch := make(chan int)  // this is going to send and receive integers

// create a buffered channel
ch := make(chan int, 5) // this got an internal capacity of 5 messages. it can store 5 messages inside channel without having reciver immeditely.

UnBuffered channels

func main() {
    wg := &sync.WaitGroup{}
    ch := make(chan int)

    wg.Add(2)
    go func (ch chan int, wg *sync.WaitGroup) {
        fmt.Println(<- ch) // receiveing the message from channel
        wg.Done()
    }(ch,wg)
    go func (ch chan int, wg *sync.WaitGroup) {
        ch <- 42 // sending the message from channel
        wg.Done()
    }(ch,wg)

    wg.Wait()
}

Here you might get a question like why can't I just do without using goroutines.

well in that case let's try doing it without goroutines.

func main() {
    ch := make(chan int)
    fmt.Println(<- ch) // receiveing the message from channel
    ch <- 42 // sending the message from channel
}

The problem we have in the above code is channels are blocking construct. To receive the message from the channel I have to have the message available or the applications going to block in this goroutine until a message does become available.

Here fmt.Println(<-ch) I am trying to receive a message from the channel but the message is not available. I have generated a message on the next line but I won't be able to reach there beacuase we won't be able to pass fmt.Println(<-ch) until a message is available. Even if I reverse the operation like this.

func main() {
    ch := make(chan int)
    ch <- 42 // sending the message from channel
    fmt.Println(<- ch) // receiveing the message from channel
}

We are going to have the opposite problem we are sending the message to the channel but there is no one to listen to it. And similarly, we won't be able to pass ch<- 42.

If we try to run this code you will get a deadlock condition. fatal error: all goroutines are sleep-deadlock!

And if we use goroutines. It works for us because one goroutine can recognize that it is looking for another message but the message is not available. In that case, the goroutine can just go to sleep and let other goroutines take over.

Buffered channels

func main() {
    wg := &sync.WaitGroup{}
    ch := make(chan int)

    wg.Add(2)
    go func (ch chan int, wg *sync.WaitGroup) {
        fmt.Println(<- ch) // receiveing the message from channel
        wg.Done()
    }(ch,wg)
    go func (ch chan int, wg *sync.WaitGroup) {
        ch <- 42 // sending the message from channel
        ch <- 43
        wg.Done()
    }(ch,wg)

    wg.Wait()
}

Here in the above code, you can see we are sending two messages but we have only one receiver. If we try to run this code we are going to get fatal error: all goroutines are sleep-deadlock! To handle this situation we need to have some sort of internal buffer in the channel. Just add this line of code to our channel ch := make(chan int, 1) . In this case, we can have one message sitting within the channel so we don't have to have a perfect match with the sender and receiver.

Channel types

  1. Bidirectional (any function that is made by make function is bidirectional that can do both send and receive messages)

  2. Send only

  3. Receive only

Example

ch := make(chan int) //  create channels are always bidirectional

func myFunction(ch chan int) {...}  // bidirectional channel

func myFunction(ch chan<- int) {...} // send-only channel

func myFunction(ch <-chan int) {...} // receive only channel

Closing channels

func main() {
    wg := &sync.WaitGroup{}
    ch := make(chan int)

    wg.Add(2)
    go func (ch chan int, wg *sync.WaitGroup) {
        fmt.Println(<- ch) // receiveing the message from channel
        wg.Done()
    }(ch,wg)
    go func (ch chan int, wg *sync.WaitGroup) {
        ch <- 42 // sending the message from channel
        close(ch)
        wg.Done()
    }(ch,wg)

    wg.Wait()
}

Here in the above code, we are closing the channel in second goroutines by close(ch) However if you run the code it will still work the same and there is no such impact of closing the channel here but what if we send a message after closing the channel?

In that case, if we send a message after the channel close see below code

go func (ch chan int, wg *sync.WaitGroup) {
        ch <- 42 // sending the message from channel
        close(ch)
        ch <- 23
        wg.Done()
}(ch,wg)

the above code will send panic panic: send on a closed channel

what if we try to receive a message after the channel close will that work? In the below code, we are going to receive the message after closing the channel.

 go func (ch chan int, wg *sync.WaitGroup) {
        fmt.Println(<- ch) // receiveing the message from channel
        close(ch)
        fmt.Println(<- ch) // 0
        wg.Done()
 }(ch,wg)

It will print 0. In short closing of channel can only be done on the sending side.

Did you find this article valuable?

Support adityakmr by becoming a sponsor. Any amount is appreciated!