Golang中合并channel的正确方法
Golang中合并channel的正确方法
大家好,我遇到了一个合并通道的问题。我正在尝试解决一个多线程任务,但测试系统总是对我的所有尝试给出“错误答案”。我的代码应该从两个通道读取值,并将 f(a) + f(b) 输出 n 次。我的基本思路如下所示。在我的测试中它似乎能工作,但在测试系统中甚至无法通过第一个测试。有人能指出我逻辑中的错误,或提供任何可能的解决方案思路吗?谢谢!
func merge(f func(int) int, ch1 <-chan int, ch2 <-chan int, out chan int, n int) {
go func() {
defer close(out)
for i := 0; i < n; i++ {
select {
case v := <-ch1:
out <- (f(v) + f(<-ch2))
case v := <-ch2:
out <- (f(v) + f(<-ch1))
}
}
}()
}
更多关于Golang中合并channel的正确方法的实战教程也可以访问 https://www.itying.com/category-94-b0.html
是否保证两个通道总是被放入相同数量的元素?为什么不按顺序读取它们呢?
a :=<- ch1
b :=<- ch2
out <- f(a) + f(b)
更多关于Golang中合并channel的正确方法的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
在大学时,如果对测试的功能或练习题有疑问,我们总是可以去找老师请教。
如果你能让他们相信你已经付出了足够的努力去自己理解问题,他们通常会和你一起查看代码,并帮助你找到问题所在。我希望你的老师也是这样的人。
我不知道完整的描述,但如果通道中至少没有 n 个条目,你的合并操作就无法工作。另外,“非阻塞”具体是什么意思?除非你使用带有 default 分支的 select 语句,否则读取通道总是会阻塞。
为自己做件好事。买一本凯瑟琳·考克斯-布戴所著的《Go并发编程:开发者工具与技巧》。阅读它,并完成其中的练习。我已经这样做了,它确实让我大开眼界,了解了如何在Go中实现并发。
项目符号准确地描述了我代码中的操作。
不过,关于 f 可能长时间运行的提示,让我认为他们可能也希望你在一个 goroutine 中执行这些操作,以避免生产者端出现超时。
另外,我不会关闭一个我不“拥有”的通道…
嗨,感谢您的帮助。您给了我一个思路,将写入输出通道的操作封装到 goroutine 中,所以我会尝试围绕这个思路进行调试。如果这还不行,我一定会联系我的老师并寻求建议。感谢两位的帮助!
我所尝试的一切都是在 goroutine 中进行的,所以我以为你建议的代码片段是包裹在 goroutine 里的 :) 是的,我试过不关闭输出通道。我也尝试过检查是否有任何通道已完成,如果是的话就停止循环。但无论如何它都不起作用,这就是为什么我绝望地试图修复它。我真的看不出我的解决方案有什么错误,而且我几乎说服自己,可能是测试系统出了问题……
go func() { defer close(out) for i := 0; i < n; i++ { select { case v := <-ch1: out ← (f(v) + f(<-ch2)) case v := <-ch2: out ← (f(v) + f(<-ch1)) } } }()
我尝试在我的代码中添加一个空的 default 分支,但没有成功。
完整描述:
You need to write function func merge(f func(int) int, ch1 <-chan int, ch2 <- chan int, out chan<- int, n int) in package main.
Description:
n times make the following
* Read one value from each channel in1 and in2, we will call it x1 and x2.
* Calculate f(x1) + f(x2)
* Write the result to out
Function merge must be non blocking , I.e. immediately returning the control
Function f may work for a long time or make some calculations
我同意 NobbZ 的评论,在这种情况下与老师确认可能是个好主意。我创建了一个我认为符合描述要求的实现,但我个人不会使用这段代码:https://play.golang.org/p/dMETV35QtKU
NobbZ 还问到是否保证通道具有相同数量的元素——如果我实现的通道出现以下情况,将无法正常工作:
- 一个或两个输入通道产生的值少于传递给
merge的n值。按照我的写法,你会遇到 panic。如果你从通道接收中移除ok值,它将永远阻塞。 - 一个或两个输入通道产生的值超过
n个。goroutine 和通道将永远不会被垃圾回收。 - 你无法对输出通道进行 range 操作,除非你在
merge中(或可能在别处)关闭它,但如果你需要转移输出通道的所有权,这可能会变得复杂。NobbZ 已经提到,关闭一个传递给函数的通道可能是一种代码异味。
由于“重复 n 次执行以下操作”的要求,你必须从生产者、合并器和消费者一路跟踪这个 n 值。如果你移除这个要求,你可以更改实现,使得在值不完全匹配时不会挂起或 panic。
func main() {
fmt.Println("hello world")
}
你的代码存在一个关键问题:在 select 的每个分支中,你从两个通道各读取了一次值,但每次循环只应读取两个通道各一个值(总共两个值)。当前逻辑会导致在某些情况下读取错误的通道组合,或重复读取同一通道。
具体来说,当 select 随机选择一个分支执行时,例如进入 case v := <-ch1:,你从 ch1 读取了 v,但随后立即在同一分支中从 ch2 读取了另一个值(通过 f(<-ch2))。这会导致两个问题:
- 如果
ch1和ch2的读取顺序很重要(例如需要配对处理),你的逻辑会破坏配对。 - 如果通道关闭或阻塞,可能引发意外行为。
正确的做法是确保每次循环从每个通道各读取一个值,然后计算 f(a) + f(b)。以下是修正后的代码:
func merge(f func(int) int, ch1 <-chan int, ch2 <-chan int, out chan int, n int) {
go func() {
defer close(out)
for i := 0; i < n; i++ {
a := <-ch1
b := <-ch2
out <- f(a) + f(b)
}
}()
}
如果通道可能阻塞或关闭,且需要确保并发安全读取,可以使用带 select 的显式同步逻辑:
func merge(f func(int) int, ch1 <-chan int, ch2 <-chan int, out chan int, n int) {
go func() {
defer close(out)
for i := 0; i < n; i++ {
var a, b int
select {
case a = <-ch1:
}
select {
case b = <-ch2:
}
out <- f(a) + f(b)
}
}()
}
但根据你的描述,通道应保证提供足够数据,因此第一种简单版本即可满足要求。


