Go - Concurrency - Channels

Source: Learn how concurrency works in Go

A channel in Go is a communication mechanism between goroutines. The idea is to send a value from one goroutine to another, using channels.

Syntax

A channel sends and receives data, to we need to indicate the type of data we want to send and receive. keyword chan as the data type for a channel, but you also need to specify the data type that will pass through the channel, like an int type.

To create a channel, you use the built-in make() function, like:

ch := make(chan int)

Operations

Send and receive data

A channel can do two operations: send data and receive data. Sending data and receiving data in channels are blocking operations.

ch <- x // sends (or write) x through channel ch
x = <-ch // x receives (or reads) data sent to the channel ch
<-ch // receives data, but the result is discarded

Close a channel

When closing a channel, we are saying that data will not be sent in that channel again.

close(ch)

If you try to send data to a closed channel, the program will panic.If you try to read data, it will return a zero value.

Example done in the goroutine post

func checkAPI(api string, ch chan string) {
    _, err := http.Get(api)
    if err != nil {
        ch <- fmt.Sprintf("ERROR: %s is down!\n", api)
        return
    }

    ch <- fmt.Sprintf("SUCCESS: %s is up and running!\n", api)
}

So the code is:

package main

import (
	"fmt"
	"net/http"
	"time"
)

func checkAPI(api string, ch chan string) {
	_, err := http.Get(api)
	if err != nil {
    // Create a string
		ch <- fmt.Sprintf("ERROR: %s is down!\n", api)
		return // we don't need the continue as we are not in a loop. We stop the goroutine execution instead.
	}
	ch <- fmt.Sprintf("SUCCESS: %s is up and running!\n", api)
}

func main() {
	start := time.Now()
	ch := make(chan string)

	apis := []string{
		"https://management.azure.com",
		"https://dev.azure.com",
		"https://api.github.com",
		"https://outlook.office.com/",
		"https://api.somewhereintheinternet.com/",
		"https://graph.microsoft.com",
	}

	for _, api := range apis {
		go checkAPI(api, ch)
	}

	elapsed := time.Since(start)
	fmt.Print(<-ch)
	fmt.Printf("Done! It took %v seconds!\n", elapsed.Seconds())
}

The output is:

ERROR: https://api.somewhereintheinternet.com/ is down!

Done! It took 0.007401217 seconds!

We only see the output for the first goroutine.

Unbuffered channels

Unbuffered channels block the sending operation until there’s someone ready to receive the data.

fmt.Print(<-ch) blocks the program until there’s someone ready to receive the data.

We can add more fmt.Print(<-ch) lines to keep reading the data from the channel… but this is not the best solution… we could add a for loop to keep reading the data from the channel.

package main

import (
    "fmt"
    "net/http"
    "time"
)

func main() {
    start := time.Now()

    apis := []string{
        "https://management.azure.com",
        "https://dev.azure.com",
        "https://api.github.com",
        "https://outlook.office.com/",
        "https://api.somewhereintheinternet.com/",
        "https://graph.microsoft.com",
    }

    ch := make(chan string)

    for _, api := range apis {
        go checkAPI(api, ch)
    }

    // Read all values from the channel
    for i := 0; i < len(apis); i++ {
        fmt.Print(<-ch)
    }

    elapsed := time.Since(start)
    fmt.Printf("Done! It took %v seconds!\n", elapsed.Seconds())
}

func checkAPI(api string, ch chan string) {
    _, err := http.Get(api)
    if err != nil {
        ch <- fmt.Sprintf("ERROR: %s is down!\n", api)
        return
    }

    ch <- fmt.Sprintf("SUCCESS: %s is up and running!\n", api)
}