【问题标题】:Multiple senders to single channel in GolangGolang中的多个发送者到单个通道
【发布时间】:2018-08-04 00:12:48
【问题描述】:

这个概念似乎很容易解释,但实施起来有点困难(“正确”)。

tl;dr 是我想运行多个将输出推送到单个通道的函数。

作为一个示例工作测试(具有多个通道),详细说明我的问题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)
}

输出:

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}]

但是,我想实现这样的目标:https://play.golang.org/p/qvC88LwkanY 输出:

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}]

但如图所示,函数 modify2 和 modify3 在视觉上似乎从未被添加。

这可能吗,还是上面的样本更可行?

【问题讨论】:

  • 如果你想让他们都推送到同一个频道,为什么不让他们都推送到播放链接中的同一个频道?
  • 你在第二个例子中的问题是你只从频道接收一次,这意味着只有 第一个 发送者到频道(在这种情况下,A,虽然它在技术上是不确定的)被接收。如果您需要所有 3 个的输出,则需要从通道接收 3 次才能获得所有输出(发送到通道不仅仅是神奇地连接所有这些值!)
  • 是否要等到所有例程完成后再打印结果?
  • 太棒了,我想我可能误解了阻塞/发送的概念,根据我的大脑,通过发送到通道,它会在 go 例程完成后全部被拉出,几乎就像跳到一个堆栈,然后拉整个堆栈。但我现在确实明白我必须“为每次推动而拉动”。非常感谢大家:)
  • play.golang.org/p/_v1r_cQ5gAS 看来 Body.Close() 并没有关闭 goroutine。

标签: go concurrency channels


【解决方案1】:

我想实现这样的目标

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

这可能吗,还是上面的样本更可行?

是的:您可以在多个 goroutine 中使用单个通道——这就是通道的设计目的。

但如图所示,函数 modify2 和 modify3 在视觉上似乎从未被添加。

你遇到的问题是你只调用了一次接收操作符:

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

当您的main goroutine 退出时,其他两个 goroutine 要么被阻止等待发送它们的结果,要么仍在构建它们的结果。

如果您将main() 函数更改为此,您可以看到如果另一个例程已准备好接收,则所有三个工作人员都将通过通道发送结果(因为您使用了无缓冲通道,发送将阻塞,直到接收器准备好了):

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)
}

哪些输出:

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}]

然后您可以更改该代码以在输出之前将接收到的元素附加到单个列表中,或者以您喜欢的任何方式格式化接收到的元素。

我认为这几乎就像弹出一个堆栈然后拉整个堆栈

对于一个已初始化的、未关闭的缓冲通道,发送语句将一个元素放入队列,接收操作将一个元素从队列中弹出并返回。它是先进先出 (FIFO) 顺序,因此它是一个队列而不是堆栈。在上面的示例中,通道是无缓冲的,因此发送必须等待 goroutine 准备好接收,而接收必须等待 goroutine 准备好发送。 Main 也是一个 goroutine。

【讨论】:

  • 谢谢你的回答,我理解你给出的概念/设计......我对阻塞/发送的理解有点偏差,我认为这几乎就像弹出一个堆栈然后拉整个堆栈。
  • 此方法 100% 有效,但一旦应用于 HTTP 相关函数,它似乎会挂起,这是我正在谈论的示例:play.golang.org/p/_v1r_cQ5gAS 似乎 Body.Close () 不会关闭 go 例程。
  • 为什么 response.Body.Close() 会关闭 goroutine?
  • 您在评论中链接的代码包含无限 for { ... } 循环。看起来你只在错误时调用os.Exit。所以我不明白为什么你的程序不会永远运行。
  • 您链接的代码是具有不同问题的不同代码。如果您需要更多帮助,我建议您将其作为新问题发布。
猜你喜欢
  • 2016-06-16
  • 2018-09-29
  • 2016-06-28
  • 2022-01-10
  • 1970-01-01
  • 1970-01-01
  • 2014-01-02
  • 2020-10-21
  • 1970-01-01
相关资源
最近更新 更多