What's the point of one-way channels in Go?

Go

Go Problem Overview


I'm learning Go and so far very impressed with it. I've read all the online docs at golang.org and am halfway through Chrisnall's "The Go Programming Language Phrasebook". I get the concept of channels and think that they will be extremely useful. However, I must have missed something important along the way, as I can't see the point to one-way channels.

If I'm interpreting them correctly, a read-only channel can only be received on and a write-only channel can only be transmitted on, so why have a channel that you can send to and never receive on? Can they be cast from one "direction" to the other? If so, again, what's the point if there's no actual constraint? Are they nothing more than a hint to client code of the channel's purpose?

Go Solutions


Solution 1 - Go

A channel can be made read-only to whoever receives it, while the sender still has a two-way channel to which they can write. For example:

func F() <-chan int {
    // Create a regular, two-way channel.
    c := make(chan int)

    go func() {
        defer close(c)

        // Do stuff
        c <- 123
    }()

    // Returning it, implicitely converts it to read-only,
    // as per the function return value.
    return c
}

Whoever calls F(), receives a channel from which they can only read. This is mostly useful to avoid potential misuse of a channel at compile time. Because read/write-only channels are distinct types, the compiler can use its existing type-checking mechanisms to ensure the caller does not try to write stuff into a channel it has no business writing to.

Solution 2 - Go

I think the main motivation for read-only channels is to prevent corruption and panics of the channel. Imagine if you could write to the channel returned by time.After. This could mess up a lot of code.

Also, panics can occur if you:

  • close a channel more than once
  • write to a closed channel

These operations are compile-time errors for read-only channels, but they can cause nasty race conditions when multiple go-routines can write/close a channel.

One way of getting around this is to never close channels and let them be garbage collected. However, close is not just for cleanup, but it actually has use when the channel is ranged over:

func consumeAll(c <-chan bool) {
    for b := range c {
        ...
    }
}

If the channel is never closed, this loop will never end. If multiple go-routines are writing to a channel, then there's a lot of book-keeping that has to go on with deciding which one will close the channel.

Since you cannot close a read-only channel, this makes it easier to write correct code. As @jimt pointed out in his comment, you cannot convert a read-only channel to a writeable channel, so you're guaranteed that only parts of the code with access to the writable version of a channel can close/write to it.

Edit:

As for having multiple readers, this is completely fine, as long as you account for it. This is especially useful when used in a producer/consumer model. For example, say you have a TCP server that just accepts connections and writes them to a queue for worker threads:

func produce(l *net.TCPListener, c chan<- net.Conn) {
    for {
        conn, _ := l.Accept()
        c<-conn
    }
}

func consume(c <-chan net.Conn) {
    for conn := range c {
        // do something with conn
    }
}

func main() {
    c := make(chan net.Conn, 10)
    for i := 0; i < 10; i++ {
        go consume(c)
    }

    addr := net.TCPAddr{net.ParseIP("127.0.0.1"), 3000}
    l, _ := net.ListenTCP("tcp", &addr)
    produce(l, c)
}

Likely your connection handling will take longer than accepting a new connection, so you want to have lots of consumers with a single producer. Multiple producers is more difficult (because you need to coordinate who closes the channel) but you can add some kind of a semaphore-style channel to the channel send.

Solution 3 - Go

Go channels are modelled on Hoare's Communicating Sequential Processes, a process algebra for concurrency that is oriented around event flows between communicating actors (small 'a'). As such, channels have a direction because they have a send end and a receive end, i.e. a producer of events and a consumer of events. A similar model is used in Occam and Limbo also.

This is important - it would be hard to reason about deadlock issues if a channel-end could arbitrarily be re-used as both sender and receiver at different times.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionburflView Question on Stackoverflow
Solution 1 - GojimtView Answer on Stackoverflow
Solution 2 - GobeatgammitView Answer on Stackoverflow
Solution 3 - GoRick-777View Answer on Stackoverflow