Channels

Like a river flows, surely to the sea

3-minute read
Made by ChickenFryBytes Studios
Table of Contents

We can use channels in order to send information among concurrent goroutines. They can be thought of as pipes in which we pass data around. A new channel is created using the syntax:


make(chan <dataType>)
// e.g. a channel for passing strings
myChannel := make(chan string)

We can then send values through the channel using the syntax:


myChannel <- "this is my message to you"

Receiving values from a channel

In order to get a value in a channel, we use the syntax:


// store value from channel into variable myMessage
myMessage := <-myChannel

Consider the following code:

package main

import "fmt"

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

	myMessages := []string{
		"Lester is ok",
		"Nana is at least 60 now",
	}

	for _, message := range myMessages {
		go func() {
			littleBirdie <- message
		}()
	}

	// get first message
	message := <-littleBirdie
	// print first message
	fmt.Println(message)

	// print second message
	fmt.Println(<-littleBirdie)
}
channels.go
Copy

The send and receive operations will block until both sender and receiver are ready. Note how we do not need to use synchronization primitives like wait groups to ensure that the program waits for each goroutine to complete before terminating.

Channels are unbuffered

Channels are, by default, unbuffered meaning they only accept sends (chan <-) once there is a corresponding receive (<- chan) in the code. A deadlock occurs when there is a send and receive operation in the same goroutine:

package main

import "fmt"

func main() {
	bits := make(chan int)
	bits <- 1

	fmt.Println("Sent a bit of 1")
	fmt.Println("Received", <-bits)
}
channels-deadlock.go
Copy

Deadlocks can also occur if the receive operations exceed the number of values passed into the channel:

package main

import "fmt"

func main() {
	bits := make(chan int)
	go func() {
		bits <- 1
		bits <- 0
	}()

	fmt.Println("Sent a bit of 1")
	fmt.Println("Received", <-bits)
	fmt.Println("Received", <-bits)

	// asking for too many values
	fmt.Println("Received", <-bits)
}
channels-deadlock-2.go
Copy

Channel buffering

Buffered channels can accept a limited number of values without needing a corresponding receiver for these values. The make command is used in the usual fashion but with a second parameter being the maximum number of values that can be sent in the channel without needing the receiver:

package main

import "fmt"

func main() {
	messages := make(chan string, 5)

	// send messages
	messages <- "Hello"
	messages <- "World!"
	messages <- "How"
	messages <- "are"
	messages <- "you?"

	// receive messages
	fmt.Println(<-messages)
	fmt.Println(<-messages)
	fmt.Println(<-messages)
	fmt.Println(<-messages)
	fmt.Println(<-messages)
}
channels-buffered.go
Copy

Of course, a deadlock results if we try to receive too many values (try duplicating the fmt.Println statement and rerun the code).

Channel synchronization

Channel directions


Support us via BuyMeACoffee