仅选择一个通道的打印输出

仅选择一个通道的打印输出

问题描述:

I am learning Go and I am on channels now. I have written a simple program using channels. I have created two channels and the channels are passed to a function which is called concurrently.

My expectation is to print output form both the channels but in reality only one channel output is getting printed:

package main

import "fmt"

func square(dat int, ch chan<- int) {

    ch <- dat * dat

}

func main() {

    resp1 := make(chan int)
    resp2 := make(chan int)

    go square(20, resp1)
    go square(10, resp2)

    select {
    case msg1 := <-resp1:
        fmt.Println(msg1)
    case msg2 := <-resp2:
        fmt.Println(msg2)
    }
}

Either the message from resp1 is printed or that from resp2 during each execution. Channels should block until something is pushed into it, right?

The Go Programming Language Specification

Select statements

A "select" statement chooses which of a set of possible send or receive operations will proceed.

Select chooses one of a set. For example,

package main

import "fmt"

func square(dat int, ch chan<- int) {

    ch <- dat * dat

}

func main() {

    resp1 := make(chan int)
    resp2 := make(chan int)

    go square(20, resp1)
    go square(10, resp2)

    // Choose one
    select {
    case msg1 := <-resp1:
        fmt.Println(msg1)
    case msg2 := <-resp2:
        fmt.Println(msg2)
    }
    // Choose the other
    select {
    case msg1 := <-resp1:
        fmt.Println(msg1)
    case msg2 := <-resp2:
        fmt.Println(msg2)
    }
}

Playground: https://play.golang.org/p/TiThqcXDa6o

Output:

100
400

You want select to select both? That's kind of the opposite of what it's for. From the Go Spec:

A "select" statement chooses which of a set of possible send or receive operations will proceed.

If one or more of the communications can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection.

If you want to read from both channels, rather than having select figure out which one is ready to be read from (or select one pseudo-randomly if both can be read from), don't use select. Just read from both:

msg1 := <-resp1:
fmt.Println(msg1)
msg2 := <-resp2:
fmt.Println(msg2)

Update

If, as @peterSO suggests, your goal is to read from both channels, starting with whichever is ready first, I'd think something like this would be a reasonable approach:

func main() {
    resp1 := make(chan int)
    resp2 := make(chan int)

    readsWanted := 0

    readsWanted += 1
    go square(20, resp1)
    readsWanted += 1
    go square(10, resp2)

    for i := 0; i < readsWanted; i++ {
        select {
        case msg1 := <-resp1:
            fmt.Println(msg1)
        case msg2 := <-resp2:
            fmt.Println(msg2)
        }
    }
}

You could of course hard-code the loop to only run twice, but I have an aversion to such things, although in this simple example it doesn't much matter.

As per your code in the select whenever any case matches will execute and main function will terminate .That is why it is only printing the single channel value. You can fix this by doing something like this :

package main

import (
    "fmt"
    "os"
    "time"
)

func square(dat int, ch chan<- int) {
    ch <- dat * dat
}

func main() {
    resp1 := make(chan int)
    resp2 := make(chan int)

    go square(20, resp1)
    go square(10, resp2)
    time.Sleep(1 * time.Second)
    for {
        select {
        case msg1 := <-resp1:
            fmt.Println(msg1)
        case msg2 := <-resp2:
            fmt.Println(msg2)
        default:
            close(resp1)
            close(resp2)
            fmt.Println("no value recieved")
            os.Exit(0)
        }
    }
}

Output

100
400
no value recieved

See in playground: https://play.golang.org/p/T9mkfrO4wNF

Selects works more like a switch in another languages (ex: Java), you need to execute that part N times as you need.