What are channels used for?

When looking through some Go code I found the following:

  ch := make(chan int)

I looked up in a online tutorial how Go Channels work:

https://tour.golang.org/concurrency/2

But I find this example unclear.

Can someone give me a easy explanation and an example of the use of channels?


Solution 1:

chan is a channel in Golang. In simple word you can think it as a box in which you put a item at one end and then pick it from other end.

Unbuffered Channels

enter image description here

Buffered Channel

enter image description here

This is the small code I have written for you to understand channels. Now change order of go routines and see the outputs. Each time output may differ.

    package main

    import (
        "fmt"
        "time"
    )

    func main() {
        messages := make(chan int)
        go func() {
            time.Sleep(time.Second * 3)
            messages <- 1
        }()
        go func() {
            time.Sleep(time.Second * 2)
            messages <- 2
        }() 
        go func() {
            time.Sleep(time.Second * 1)
            messages <- 3
        }()
        go func() {
            for i := range messages {
                fmt.Println(i)
            }
        }()
        go func() {
            time.Sleep(time.Second * 1)
            messages <- 4
        }()
        go func() {
            time.Sleep(time.Second * 1)
            messages <- 5
        }()
        time.Sleep(time.Second * 5)
    }

For best understanding visit this blog where go routines and channels are described in GUI.

Visit http://divan.github.io/posts/go_concurrency_visualize/

Solution 2:

I think the spec is pretty clear on this. Spec: Channel types:

A channel provides a mechanism for concurrently executing functions to communicate by sending and receiving values of a specified element type.

When you have multiple goroutines which are executed concurrently, channels provide the easiest way to allow the goroutines to communicate with each other.

One way for communication would be via a "shared" variable which is visible to both goroutines, but that would require proper locking / synchronized access.

Instead, Go favors channels. Quoting from Effective Go: Share by communicating:

Do not communicate by sharing memory; instead, share memory by communicating.

So instead of putting messages into a shared slice for example, you can create a channel (visible to both goroutines), and without any external synchronization / locking, one goroutine can send messages (values) via the channel, and the other goroutine can receive them.

Only one goroutine has access to the value at any given time. Data races cannot occur, by design.

So in fact, any number of goroutines can send values on the same channel, and any number of goroutines can receive values from it, still without any further synchronization. See related question for more details: If I am using channels properly should I need to use mutexes?

Channel example

Let's see an example where we start 2 additional goroutines for concurrent computation purposes. We pass a number to the first, which adds 1 to it, and delivers the result on a 2nd channel. The 2nd goroutine will receive a number, multiply it by 10 and deliver it to the result channel:

func AddOne(ch chan<- int, i int) {
    i++
    ch <- i
}

func MulBy10(ch <-chan int, resch chan<- int) {
    i := <-ch
    i *= 10
    resch <- i
}

This is how it can be called / used:

func main() {
    ch := make(chan int)
    resch := make(chan int)

    go AddOne(ch, 9)
    go MulBy10(ch, resch)

    result := <-resch
    fmt.Println("Result:", result)
}

Communicating via channels also takes care of goroutines waiting for each other. In this example it means MulBy10() will wait until AddOne() delivers the incremented number, and main() will wait for MulBy10() before printing the result. Output as expected (try it on the Go Playground):

Result: 100

Language support

There are a number of language constructs which are for easy use of channels, for example:

  • The for ... range on a channel loops over values received from a channel, until the channel is closed.
  • The select statement can be used to list multiple channel operations such as send on a channel and receive from a channel, and the one that can proceed without blocking will be selected (randomly if there are multiple ones that can proceed; and will block if none is ready).
  • There is a special form of the receive operator which allows you to check if the channel was closed (besides receiving a value): v, ok := <-ch
  • The builtin len() function tells the number of elements queued (unread); the builting cap() function returns the channel buffer capacity.

Other uses

For a more practical example, see how channels can be used to implement a worker pool. A similar use is distributing values from a producer to consumer(s).

Another practical example is to implement a memory pool using buffered channels.

And yet another practical example is an elegant implementation of a broker.

A channel is often used to timeout some blocking operation, utilizing a channel returned by time.After() which "fires" after the specified delay / duration ("fires" means a value will be sent on it). See this example for demonstration (try it on the Go Playground):

ch := make(chan int)

select {
case i := <-ch:
    fmt.Println("Received:", i)
case <-time.After(time.Second):
    fmt.Println("Timeout, no value received")
}

It can be used to wait for a maximum time amount for some value, but if other goroutines cannot supply the value by that time, we may decide to do something else instead.

Also a special form of communication may be just to signal completion of some operation (without actually sending any "useful" data). Such case may be implemented by a channel with any element type, e.g. chan int, and sending any value on it, e.g. 0. But since the sent value holds no information, you may declare it like chan struct{}. Or even better, if you only need a one-time signalling, you may just close the channel which can be intercepted on the other side using for ... range, or receiving from it (as receiving from a closed channel proceeds immediately, yielding the zero value of the element type). Also know that even though a channel may be used for this kind of signalling, there's a better alternative for this: sync.WaitGroup.

Further reading

It's worth knowing about the channel axioms to avoid suprising behaviour: How does a non initialized channel behave?

The Go Blog: Share Memory By Communicating

The Go Blog: Go Concurrency Patterns: Pipelines and cancellation

The Go Blog: Advanced Go Concurrency Patterns

Ardan labs: The Nature Of Channels In Go