75

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?

burfl
  • 2,138
  • 2
  • 20
  • 18

3 Answers3

110

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, implicitly converts it to read-only,
    // as per the function return type.
    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.

JohnAllen
  • 7,317
  • 9
  • 41
  • 65
jimt
  • 25,324
  • 8
  • 70
  • 60
  • But on the receiving end, the caller can't cast it back to two way? That's sort of what I'm getting at here. It can only be implicitly converted by returning it? – burfl Nov 28 '12 at 02:01
  • 14
    You can't convert it back to a writable version. The compiler will complain with `cannot convert c (type <-chan int) to type chan int`. Well, you can using the `unsafe` package, but that's a different story entirely. All bets are off when this package comes into play. – jimt Nov 28 '12 at 02:11
10

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.

beatgammit
  • 19,817
  • 19
  • 86
  • 129
  • Thank you very much! Lots of good information here. I don't know that it answers the original question any better than @jimt, so I'll leave that as the accepted answer, but this does a very good job of clarifying subsequent questions I've noted in comments. I'll definitely upvote. – burfl Nov 28 '12 at 19:34
  • I wasn't expecting to get the accepted answer =). I just thought this information would be useful. – beatgammit Nov 28 '12 at 22:45
8

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.

Rick-777
  • 9,714
  • 5
  • 34
  • 50
  • Thanks for adding. I find this valuable. Perhaps this should be a new question, but is it safe for multiple senders to share one "end" and multiple receivers to read from the other? – burfl Nov 28 '12 at 17:54
  • I don't know whether having multiple writers or readers is supported by Go channels. One of the answers here asserts it does, whereas the language reference implies it doesn't (but is a bit ambiguous). It hardly matters; by contrast, the JCSP Java channel library differentiated between one-to-one and any-to-one channels (etc) because it *has* to, whereas the Go compiler could in principle work it out automatically. – Rick-777 Nov 29 '12 at 23:04
  • I found a good answer for this here https://stackoverflow.com/questions/15715605/multiple-goroutines-listening-on-one-channel/15721380#15721380 – Rick-777 Sep 05 '17 at 11:30