Channels are a way for goroutines to communicate with eachother. A channel type is defined with the keyword chan.

Because goroutines run concurrently, they can’t simply pass data from one goroutine to another. Channels are needed.

How do channels work?

If you type c <- "message", “message” gets send to the channel. Then msg := <- c means receive the message and store it in variable msg.

Example

Goroutines

In this example there are two goroutines (f, f2). These goroutines communicate via a channel, c chan string.

In goroutine f(c chan string) we send a message into the channel. In goroutine f2(c chan string) the message is received, stored and printed to the screen.


package main

import "fmt"

func f(c chan string) {
    c <- "f() was here"
}

func f2(c chan string) {
    msg := <- c
    fmt.Println("f2",msg)
}


func main() {
   var c chan string = make(chan string)
   go f(c)
   go f2(c)

   fmt.Scanln()
}
    $ go run example.go
    f2 f() was here

Example 1: Unbuffered Channel

A channel is a built-in data structure that allows you to send and receive values between goroutines. Channels can be either buffered or unbuffered. In an unbuffered channel, each send operation must be matched by a receive operation, making it a synchronous communication mechanism. Here’s an example of using an unbuffered channel:

package main

import (
    "fmt"
    "time"
)

func worker(done chan bool) {
    fmt.Println("Worker started")
    time.Sleep(time.Second * 2) // simulate some work
    fmt.Println("Worker finished")

    done <- true // send a signal to the channel that the work is done
}

func main() {
    done := make(chan bool)

    go worker(done) // start a worker goroutine

    <-done // wait for the worker goroutine to finish and receive the signal
    fmt.Println("Main function exiting")
}

In this example, the worker() function simulates some work by sleeping for 2 seconds. Once the work is done, it sends a signal to the done channel by writing true to it. The main() function creates the done channel and starts a worker goroutine by calling go worker(done). It then waits for the worker to finish by receiving a signal from the done channel using the <-done syntax.

Example 2: Buffered Channel

In contrast to unbuffered channels, buffered channels allow you to send a specified number of values without needing to wait for a receive operation. Here’s an example:

package main

import (
    "fmt"
)

func producer(c chan int) {
    for i := 0; i < 5; i++ {
        c <- i // send the value to the channel
    }
    close(c) // close the channel after all values have been sent
}

func main() {
    c := make(chan int, 2) // create a buffered channel with capacity 2
    go producer(c)

    for i := range c { // receive the values from the channel
        fmt.Println(i)
    }
}

In this example, the producer() function sends 5 values to the channel c. Since c is a buffered channel with capacity 2, the first 2 values are sent immediately, and the remaining values are buffered until they can be received by the main() function’s loop. Finally, the channel is closed to signal that all values have been sent. The main() function receives the values from the channel using the range syntax, which automatically stops when the channel is closed.

Exercises

  • When do you need channels?
  • How can you send data into a channel?
  • How can you read data from a channel?