简体   繁体   中英

Multiple senders to single channel in Golang

The concept seems simple to explain, but a tad harder to implement ("correctly").

The tl;dr is I want to run multiple functions that push output into a single channel.

As a sample working test (with multiple channels), to elaborate on my question https://play.golang.org/p/1ztCvPFLXKv

package main

import (
    "fmt"
    "time"
)

type intTest struct {
    ID     int
    Number int
}

func modify1(channelID string, res chan []intTest) {
    s := []intTest{}
    for i := 0; i < 10; i++ {
        fmt.Printf("Adding inside: %s\n", channelID)
        s = append(s, intTest{i, 0})
        time.Sleep(100 * time.Millisecond)
    }
    res <- s
}
func modify2(channelID string, res chan []intTest) {
    s := []intTest{}
    for i := 10; i < 20; i++ {
        fmt.Printf("Adding inside: %s\n", channelID)
        s = append(s, intTest{i, 0})
        time.Sleep(200 * time.Millisecond)
    }
    res <- s
}
func modify3(channelID string, res chan []intTest) {
    s := []intTest{}
    for i := 20; i < 30; i++ {
        fmt.Printf("Adding inside: %s\n", channelID)
        s = append(s, intTest{i, 0})
        time.Sleep(300 * time.Millisecond)
    }
    res <- s
}

func main() {
    channelA := make(chan []intTest)
    channelB := make(chan []intTest)
    channelC := make(chan []intTest)

    go modify1("A", channelA)
    go modify2("B", channelB)
    go modify3("C", channelC)

    b := append(<-channelA, <-channelB...)
    b = append(b, <-channelC...)
    fmt.Println(b)
}

Output:

Adding inside: C
Adding inside: A
Adding inside: B
..snip..
Adding inside: C
Adding inside: C
Adding inside: C
[{0 0} {1 0} {2 0} {3 0} {4 0} {5 0} {6 0} {7 0} {8 0} {9 0} {10 0} {11 0} {12 0} {13 0} {14 0} {15 0} {16 0} {17 0} {18 0} {19 0} {20 0} {21 0} {22 0} {23 0} {24 0} {25 0} {26 0} {27 0} {28 0} {29 0}]

However, I would like to achieve something like this: https://play.golang.org/p/qvC88LwkanY Output:

Adding inside: C
Adding inside: A
Adding inside: B
..snip
Adding inside: B
Adding inside: A
Adding inside: C
[{0 0} {1 0} {2 0} {3 0} {4 0} {5 0} {6 0} {7 0} {8 0} {9 0}]

but as shown the functions modify2 & modify3 visually seems like they never get added.

Is this possible, or is the top sample more feasible?

I would like to achieve something like this

channelA := make(chan []intTest)
go modify1("A", channelA)
go modify2("B", channelA)
go modify3("C", channelA)

Is this possible, or is the top sample more feasible?

Yes: you can use a single channel across multiple goroutines -- that's what channels are designed for.

but as shown the functions modify2 & modify3 visually seems like they never get added.

The problem you have there is that you only called the receive operator once:

b := append(<-channelA)
fmt.Println(b)

The other two goroutines were either blocked waiting to send their result, or still building their result, when your main goroutine exited.

If you change your main() function to this, you can see that all three workers will send their results over the channel if another routine is ready to receive (because you used an unbuffered channel, sends will block until a receiver is ready):

func main() {
    ch := make(chan []intTest)

    go modify1("A", ch)
    go modify2("B", ch)
    go modify3("C", ch)
    fmt.Println(<-ch)
    fmt.Println(<-ch)
    fmt.Println(<-ch)
}

Which outputs:

Adding inside: C
Adding inside: A
Adding inside: B
Adding inside: A
Adding inside: A
Adding inside: B
Adding inside: C
Adding inside: A
Adding inside: B
Adding inside: A
Adding inside: A
Adding inside: B
Adding inside: C
Adding inside: A
Adding inside: A
Adding inside: B
Adding inside: A
Adding inside: C
Adding inside: A
Adding inside: B
[{0 0} {1 0} {2 0} {3 0} {4 0} {5 0} {6 0} {7 0} {8 0} {9 0}]
Adding inside: C
Adding inside: B
Adding inside: B
Adding inside: C
Adding inside: B
Adding inside: C
Adding inside: B
[{10 0} {11 0} {12 0} {13 0} {14 0} {15 0} {16 0} {17 0} {18 0} {19 0}]
Adding inside: C
Adding inside: C
Adding inside: C
[{20 0} {21 0} {22 0} {23 0} {24 0} {25 0} {26 0} {27 0} {28 0} {29 0}]

You can then change that code to append received elements to a single list prior to output, or otherwise format the received elements in whatever way you like.

I thought it's almost like popping onto a stack and then pulling the whole stack

With an initialized, not-closed, buffered channel, a send statement puts one element onto a queue, and a receive operation pops one element off the queue and returns it. It's first in first out (FIFO) order, so it's a queue rather than a stack. In the examples above the channel is unbuffered, so a send has to wait for a goroutine ready to receive, and a receive has to wait for a goroutine ready to send. Main is a goroutine as well.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM