Channels

Channels are a duck typing feature.


Description

Channels are a pipeline. Their primary use case is passing values between two goroutines.

Channels must be initialized like:

ch := make(chan string)

By default a channel is unbuffered, meaning that it halts on 'sends' until a value can be 'received'. To instead initialize a channel with a buffer that can queue up to N values before halting, try:

ch := make(chan string, 2)

Values are sent and received like:

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

    ch <- "foo"

    fmt.Println(<-ch)
}

Function Signatures

Function signatures can distinguish between a channel that can be sent on and a channel that can be received on.

func i_only_send(ch chan<- string) {
    ch <- "foo"
}

func i_only_receive(ch <-chan string) {
    fmt.Println(<-ch)
}

func i_can_do_anything(ch chan string) {
    ch <- "foo"
    fmt.Println(<-ch)
}

Synchronization

A channel can be used in a purposefully blocking manner to force synchronization across goroutines.

func worker(done chan struct{}) {
    time.Sleep(3 * time.Second)

    done <- struct{}{}
}

func main() {
    done := make(chan struct{}, 1)
    go worker(done)

    <-done    //`main` waits until `worker` sends a value through `done`
}

Closing

Aside from sending a value, a close event can also be sent.

func sending_something(ch chan<- string) {
    ch <- "foo"
    time.Sleep(3 * time.Second)
    close(ch)
}

There are two ways to read the close event on the receiving side. First, by receiving the optional second value, as in:

_, more := <-ch
if !more {
    fmt.Println("no more!")
}

This can be used like:

func doing_something(ch <-chan string) {
    s, ok := <-ch
    if ok {
        fmt.Println(s)
        s, ok = <-ch
    }
}

Second, when iterating over the channel, a close event ends the loop.

func doing_something(ch <-chan string) {
    for s := range ch {
        fmt.Println(s)
    }
}

A close event can be deferred like:

defer close(ch)

A receiver should never close a channel. If a design like this is necessary, use two channels instead.

func doing_something(rx <-chan string, tx chan<- struct{}) {
    for s := range rx {
        if s == "magic phrase" {
            tx <- struct{}{}
            return
        }
        fmt.Println(s)
    }
}

Switching

A receiver can switch between channels based on which is ready. If multiple are ready, one is picked randomly.

func summation(ch chan int, quit chan struct{}) {
    sum := 0
    for {
        select {
        case i <-ch:
            sum += i

        case <-quit:
            fmt.Println(sum)

        default:
            time.Sleep(3 * time.Second)
        }
    }
}


CategoryRicottone

Go/Channels (last edited 2025-12-30 00:32:14 by DominicRicottone)