Go - Concurrency - Buffered channels

Source: Learn how concurrency works in Go

Buffered channels

Channels are unbuffered by default. That means they accept a send operation only if there’s a receive operation. Otherwise, the program will be blocked waiting forever.

Buffered channels send and receive data without blocking the program. They behaves like a queue. You can limit the size of this queue, when you crete the channel:

ch := make(chan int, 10)
  • Every time you send something to the channel, the element is added to the queue
  • A receive operation removes the element from the queue
  • When the channel is full, any send operation simply waits until there’s space to hold the data
  • If the channel is empty and there’s a read operation, it’s blocked until there’s something to read

We recommend that when you use channels, you always use goroutines

Unbuffered vs. buffered channels

Unbuffered channels communicate synchronously. They guarantee that every time you send data, the program gets blocked until someone is reading from the channel.

Buffered channels decouple the send and receive operations

Channel directions

When you use channels as parameters to a function, you can specify whether a channel is meant to send or receive data:

chan<- int // it's a channel to only send data
<-chan int // it's a channel to only receive data

Example:

package main

import "fmt"

// Sending data to a channel
func send(ch chan<- string, message string) {
    fmt.Printf("Sending: %#v\n", message)
    ch <- message
}

// Receiving data from a channel
func read(ch <-chan string) {
    fmt.Printf("Receiving: %#v\n", <-ch)
}

func main() {
    ch := make(chan string, 1)
    send(ch, "Hello World!")
    read(ch)
}

Multiplexing

How to interact with more than one channel simultaneously by using the select keyword. The select statement works like a switch statement but for channels.

An essential aspect of the select statement is that it finishes its execution after it processes an event. If you want to wait for more events to happen, you might need to use a loop.

package main

import (
    "fmt"
    "time"
)

func process(ch chan string) {
    time.Sleep(3 * time.Second)
    ch <- "Done processing!"
}

func replicate(ch chan string) {
    time.Sleep(1 * time.Second)
    ch <- "Done replicating!"
}

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)
    go process(ch1)
    go replicate(ch2)

    for i := 0; i < 2; i++ {
        select {
        case process := <-ch1:
            fmt.Println(process)
        case replicate := <-ch2:
            fmt.Println(replicate)
        }
    }
}

Output:

Done replicating!
Done processing!