13

My question arises from trying to read a channel, if I can, or write it, if I can, using a select statement.

I know that channels specified like make(chan bool, 1) are buffered, and part of my question is what is the difference between that, and make(chan bool) -- which this page says is the same thing as make(chan bool, 0) --- what is the point of a channel that can fit 0 values in it?

See playground A:

chanFoo := make(chan bool)

for i := 0; i < 5; i++ {
    select {
    case <-chanFoo:
        fmt.Println("Read")
    case chanFoo <- true:
        fmt.Println("Write")
    default:
        fmt.Println("Neither")
    }
}

A output:

Neither
Neither
Neither
Neither
Neither

(Removing the default case results in a deadlock!!)

Now see playground B:

chanFoo := make(chan bool, 1)   // the only difference is the buffer size of 1

for i := 0; i < 5; i++ {
    select {
    case <-chanFoo:
        fmt.Println("Read")
    case chanFoo <- true:
        fmt.Println("Write")
    default:
        fmt.Println("Neither")
    }
}

B output:

Write
Read
Write
Read
Write

In my case, B output is what I want. What good are unbuffered channels? All the examples I see on golang.org appear to use them to send one signal/value at a time (which is all I need) -- but as in playground A, the channel never gets read or written. What am I missing here in my understanding of channels?

Matt
  • 22,721
  • 17
  • 71
  • 112
  • "what is the point of a channel that can fit 0 values in it" the second parameter here means buffer size, so that is simply a channel without buffers (un-buffered channel) – starrify Nov 18 '13 at 06:27
  • 2
    Example A would work if Read and Write are in different goroutines. Default channels, without capacity, are unbuffered: sending something blocks the sender until the receiver reads from the channel, so you need to put both in different goroutines. – siritinga Nov 18 '13 at 06:29
  • @siritinga If you expand that into an answer, I think that's it. So unbuffered channels block unless somebody is *already* waiting for it, whereas a buffered channel of size 1 will hold the value *until* somebody is ready to receive it. – Matt Nov 18 '13 at 06:30
  • See also: [Why using unbuffered channel in the the same goroutine gives a deadlock](https://stackoverflow.com/questions/18660533/why-using-unbuffered-channel-in-the-the-same-goroutine-gives-a-deadlock) – user Nov 18 '15 at 17:47

3 Answers3

15

what is the point of a channel that can fit 0 values in it

First I want to point out that the second parameter here means buffer size, so that is simply a channel without buffers (un-buffered channel).

Actually that's the reason why your problem is generated. Un-buffered channels are only writable when there's someone blocking to read from it, which means you shall have some coroutines to work with -- instead of this single one.

Also see The Go Memory Model:

A receive from an unbuffered channel happens before the send on that channel completes.

starrify
  • 14,307
  • 5
  • 33
  • 50
  • 1
    Bingo -- I haven't yet gotten all the way through the memory model document. That clears things up. Thanks! – Matt Nov 18 '13 at 06:40
4

An unbuffered channel means that read and writes from and to the channel are blocking.

In a select statement:

  • the read would work if some other goroutine was currently blocked in writing to the channel
  • the write would work if some other goroutine was currently blocked in reading to the channel
  • otherwise the default case is executed, which happens in your case A.
Simon
  • 31,675
  • 9
  • 80
  • 92
4

Unbuffered channels (created without capacity) will block the sender until somebody can read from them, so to make it work as you expect, you should use two goroutines to avoid the deadlock in the same thread.

For example, with this code: http://play.golang.org/p/KWJ1gbdSqf

It also includes a mechanism for the main func to detect when both goroutines have finished.

package main

import "fmt"

func send(out, finish chan bool) {
    for i := 0; i < 5; i++ {
        out <- true
        fmt.Println("Write")
    }
    finish <- true
    close(out)
}

func recv(in, finish chan bool) {

    for _ = range in {
        fmt.Println("Read")
    }
    finish <- true

}

func main() {
    chanFoo := make(chan bool)
    chanfinish := make(chan bool)

    go send(chanFoo, chanfinish)
    go recv(chanFoo, chanfinish)

    <-chanfinish
    <-chanfinish
}

It won't show the alternate Read and Write, because as soon as send writes to the channel, it is blocked, before it can print the "Write", so the execution moves to recv that receives the channel and prints "Read". It will try to read the channel again but it will be blocked and the execution moves to send. Now send can write the first "Write", send to the channel (without blocking because now there is a receiver ready) and write the second "Write". In any case, this is not deterministic and the scheduler may move the execution at any point to any other running goroutine (at least in the latest 1.2 release).

siritinga
  • 4,063
  • 25
  • 38