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

11 回复

是否保证两个通道总是被放入相同数量的元素?为什么不按顺序读取它们呢?

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 里的 :) 是的,我试过不关闭输出通道。我也尝试过检查是否有任何通道已完成,如果是的话就停止循环。但无论如何它都不起作用,这就是为什么我绝望地试图修复它。我真的看不出我的解决方案有什么错误,而且我几乎说服自己,可能是测试系统出了问题……

NobbZ:

a :=<- ch1 b :=<- ch2 out ← f(a) + f(b)

我试过这个,但它没有奏效:( 任务描述的唯一限制是合并函数应该是非阻塞的,它没有提到通道的大小是否相同。所以我为此挣扎了两天,试图弄清楚哪些边界情况可能会破坏我的代码。

a :=<- ch1 b :=<- ch2 out ← f(a) + f(b)
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 还问到是否保证通道具有相同数量的元素——如果我实现的通道出现以下情况,将无法正常工作:

  • 一个或两个输入通道产生的值少于传递给 mergen 值。按照我的写法,你会遇到 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))。这会导致两个问题:

  1. 如果 ch1ch2 的读取顺序很重要(例如需要配对处理),你的逻辑会破坏配对。
  2. 如果通道关闭或阻塞,可能引发意外行为。

正确的做法是确保每次循环从每个通道各读取一个值,然后计算 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)
        }
    }()
}

但根据你的描述,通道应保证提供足够数据,因此第一种简单版本即可满足要求。

回到顶部