通道就绪后,从多个通道中选择所有值

I'm new in golang and I faced with the problem.

I have several channels.

Some payload gets to this channels in different time.

How I can get all values one by one from channels in the time when channel ready to spit it.

For example I wrote this code:

package main

import (
    "fmt"
    "time"
    "math/rand"
)

func main() {
    arr1 := []int8{1,2,3,4,5}
    arr2 := []int8{6,7,8,9,10}

    c1 := make(chan int8)
    c2 := make(chan int8)

    go func() {
        for _, val := range arr1 {
            time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
            c1 <- val
        }
    }()
    go func() {
        for _, val := range arr2 {
            time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
            c2 <- val
        }
    }()

    select {
        case res1 := <- c1:
            fmt.Println(res1)
        case res2 := <- c2:
            fmt.Println(res2)
    }

    fmt.Println("Hello, test")
}

But in this case, I get only first value, from one of the channels.

Please, give me advise how to solve my issue.

Link to go-play https://play.golang.org/p/FOmkP57YCyR

You have to do couple of things.

1) Make sure you close channels once you are done with source. 2) Iterate over channels until it's closed.

Example:

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    arr1 := []int8{1, 2, 3, 4, 5}
    arr2 := []int8{6, 7, 8, 9, 10}

    c1 := make(chan int8)
    c2 := make(chan int8)

    go func() {
        for _, val := range arr1 {
            time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
            c1 <- val
        }
        close(c1)
    }()
    go func() {
        for _, val := range arr2 {
            time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
            c2 <- val
        }
        close(c2)
    }()

    _c1 := true
    _c2 := true
    var res1, res2 int8

    for _c1 == true || _c2 == true {
        select {
        case res1, _c1 = <-c1:
            if _c1 == true {
                fmt.Println(res1)
            }
        case res2, _c2 = <-c2:
            if _c2 == true {
                fmt.Println(res2)
            }
        }
    }

    fmt.Println("Hello, test")
}

On execution, I got following output on screen.

6
1
7
2
3
4
8
5
9
10
Hello, test

Select does not wait for go routines. To achieve that you should wrap it in for statement. In this way select will be running until one of the cases returns and breaks out of for statement.

for {
   select {
      ...

You can also use buffered channels which are non-blocking and wait groups. Like this:

arr1 := []int8{1,2,3,4,5}
arr2 := []int8{6,7,8,9,10}

c1 := make(chan int8, len(arr1))
c2 := make(chan int8, len(arr2))

var wg sync.WaitGroup

wg.Add(1) // First wait group
go func() {
    for _, val := range arr1 {
        time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
        c1 <- val
    }
    wg.Done()
}()

wg.Add(1) // Second wait group
go func() {
    for _, val := range arr2 {
        time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
        c2 <- val
    }
    wg.Done()
}()

// executed after wg.Done() is called 2 times since we have 2 wait groups
wg.Wait() 

// We are not writing to channels anymore so we can close them.
close(c1)
close(c2)

for value := range c1 {
    fmt.Println(value)
}

for value := range c2 {
    fmt.Println(value)
}


fmt.Println("Hello, test")

You don't have to use 2 channels. Just use 1 channel and store values into it from multiple goroutines. Channel is lightweight thread connector that is fast and can be instantiated multiple times to store values from multiple goroutines.

The problem with your code is that it has no loop to loop over values from goroutines channel. You only print it once using select. select makes other goroutines wait until it executes one of possible cases it has. If all of the cases are possible, then it selects randomly to executes.

The reason you only got one value from your channel is because when your goroutines are working, they store values from the array to channel sequentially. while this happen, you call select statement in your main thread with the cases to get the values from the channel in your goroutines. Since you don't loop over the channels, you'll only get one value from the channel which is the value it firstly received. In this case, you loop array sequentially into channel in your goroutines, hence you'll get the first index of the array since it's the value that will be firstly sent to select statement in main thread. All of your select cases are possible to be executed, hence it will execute one of the case randomly and you'll get first index in one of those arrays.

To fix this, you need to loop over the channel to get the values stored in it one by one. Beside, you also need to synchronize all the threads in order to avoid deadlock conditions, which is happened when your main thread doesn't know when to stop invoking channel from goroutines since they work asynchronously. Your channel is ready to spit the value right after it gets its value inside goroutines and invoked into main thread in loop. Here is the code:

package main

import (
    "fmt"
    "time"
    "math/rand"
    "sync"
)

// writer set numbers from array to channel
func writer(ch chan int, arr []int ,wgwrite *sync.WaitGroup) {
    defer wgwrite.Done()
    for _, val := range arr {
        time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
        ch <- val
    }
}

// reader receive input from writer channels and print them all 
func reader(ch chan int, wgread *sync.WaitGroup) {
  defer wgread.Done()
  for i:= range ch {
    fmt.Println(i)
  }
  fmt.Println("Hello, test")
}

func main() {
    arr1 := []int{1,2,3,4,5}
    arr2 := []int{6,7,8,9,10}
    ch := make(chan int)
    wgwrite := &sync.WaitGroup{}
    wgread  := &sync.WaitGroup{}

    wgwrite.Add(2)
    go writer(ch, arr1, wgwrite)
    go writer(ch, arr2, wgwrite)

    wgread.Add(1)
    go reader(ch, wgread)

    wgwrite.Wait()
    close(ch)
    wgread.Wait()
}

https://play.golang.org/p/32Fgetq_Zu7

Hope it helps.