Concurrency in Go: Part 2

This article is the last part in a two-part series about concurrency in Go. Check out the first part here.

In Part I of this series, we covered the basics of concurrency in Go and how to implement goroutines, multiple goroutines.

In this blog we will look into Channels: creating and closing channels, unidirectional channels.

Channels

A channel is a medium through which a goroutine communicates with another goroutine. Or we can say it is a medium which allows to send data from one goroutine to another goroutine with the channel operator, <-.

In other words, we can visualize channels like a pipe between 2 goroutines through which data passed.

The direction of <- operator indicates whether the data is received or send.

In the channel, the send and receive operation is blocked until another side is not ready by default. It allows goroutine to synchronize with each other without explicit locks or condition variables. This makes goroutines to communicate more effectively.

By default channel is bidirectional, means the goroutines can send or receive data through the same channel as shown in the below image:

Creating a channel:

We can create a channel in 2 ways: one way is using chan keyword and other is using make() function.

  1. var Channel_name chan Type

  2. channel_name:= make(chan Type)

a channel can only transfer data of the same type, different types of data are not allowed to transport from the same channel.

var msg chan int --> implies that msg channel can send or receive data of type int only.

Send and Receive data from Channel:

ch <- a    // Send a to channel ch.
<-ch  // Receive from ch
a := <-ch  // Receive from ch, and store value to a.
a, ok := <-ch  //ok will be false if channel is close
close(ch) //closing a channel

Example:

package main
import "fmt"
func main() {

    myChannel := make(chan string)

    go func() { myChannel <- "ping" }()

    msg := <-myChannel
    fmt.Println(msg)
}

Output: ping

When we run the program the "ping" message is successfully passed from one goroutine to another via our channel.

Example:

package main

import (
    "fmt"
)

func display(data chan string) {
    fmt.Println("Hello, goroutine")
    data <- "channel data" 
}
func main() {
    data := make(chan string)
    go display(data)
    a := <-data
    fmt.Println("main function, received", a)
}

Output:
Hello, goroutine
main function, received channel data

In the above example, we have created a data channel of type string and called display goroutine with data as parameter. Next a := <-data line of code is blocking which means that until some Goroutine writes data to the data channel, the control will not move to the next line of code. Hence this eliminates the need for the time.Sleep which was present in the original program to prevent the main Goroutine from exiting.

Now we have our main Goroutine blocked waiting for data on the data channel. The display Goroutine receives this channel as a parameter, prints Hello, goroutine and then writes to the data channel. When this write is complete, the main Goroutine receives the data from the data channel, it is unblocked and then the text main function, received channel data is printed.

The above examples are bidirectional channels, that is data can be both sent and received on them. It is also possible to create unidirectional channels, which means channels that can either send or receive data.

Unidirectional Channels

package main

import "fmt"

func display(myChannel chan<- string) {
    myChannel <- "ping"
}

func main() {
    myChannel := make(chan<- string)
    go display(myChannel)
    msg := <-myChannel
    fmt.Println(msg)
}

Output:
./prog.go:12:11: invalid operation: cannot receive from send-only channel myChannel (variable of type chan<- string)

In the above example, we created send only channel using chan<- string as the arrow pointing to chan. When we try to receive data from a send only channel in display function, the compiler will give invalid operation as shown in output above.

But what is the use of send only channels when we can't receive data from them. Here where the conversion of bidirectional channel to a send only or receive only channel comes into picture but not the vice versa.

package main

import "fmt"

func display(myChannel chan<- string) {
    myChannel <- "ping"
}

func main() {
    myChannel := make(chan string)
    go display(myChannel)
    msg := <-myChannel
    fmt.Println(msg)
}

Output: ping

In the above example, we are creating a bidirectional channel myChannel using make(chan string). It is passed as parameter to display goroutine where myChannel is converted to send only channel in the func argument chan<- string This way the channel is birectional in main but send only channel in display goroutine. Hence we get the output as ping.

Closing a channel

Senders have the ability to close the channel to notify receivers that no more data will be sent on the channel.

You can close a channel with the help of close() function. This is an in-built function and sets a flag which indicates that no more value will send to this channel.

close(myChannel)

You can also close a channel using the below syntax. Receivers can use an additional variable while receiving data from the channel to check whether the channel has been closed.

v, ok := <- myChannel

Here, if the value of ok is true which means the channel is open hence read operations can be performed. And if the value of is false which means the channel is closed then read operations can't be performed.

Thank you!!

0
Subscribe to my newsletter

Read articles from SHIVANI GANIMUKKULA directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

SHIVANI GANIMUKKULA
SHIVANI GANIMUKKULA