2

我试图在使用通道中实现堆算法。下面的代码仅在屏幕上打印切片时工作正常,但是当使用通道将数组传递到主函数上的 for/range 循环时,会发生一些意外行为,并且切片/数组以重复打印并且并非所有排列都是发送。我想也许我在主要功能能够打印结果之前关闭了通道,但我不希望重复打印。为什么会发生这种情况,我怎样才能让它发挥作用。

package main

import "fmt"

func perm(a []int64) {
    var n = len(a)
    var c = make([]int, n)
    fmt.Println(a)
    i := 0
    for i < n {
        if c[i] < i {
            if i%2 == 0 {
                a[0], a[i] = a[i], a[0]
            } else {
                a[c[i]], a[i] = a[i], a[c[i]]
            }
            fmt.Println(a)
            c[i]++
            i = 0
        } else {
            c[i] = 0
            i++
        }
    }
}

func permch(a []int64, ch chan<- []int64) {
    var n = len(a)
    var c = make([]int, n)
    ch <- a
    i := 0
    for i < n {
        if c[i] < i {
            if i%2 == 0 {
                a[0], a[i] = a[i], a[0]
            } else {
                a[c[i]], a[i] = a[i], a[c[i]]
            }
            ch <- a
            c[i]++
            i = 0
        } else {
            c[i] = 0
            i++
        }
    }
    close(ch)
}

func main() {
    var i = []int64{1, 2, 3}
    fmt.Println("Without Channels")
    perm(i)
    ch := make(chan []int64)
    go permch(i, ch)
    fmt.Println("With Channels")
    for slc := range ch {
        fmt.Println(slc)
    }

}
4

2 回答 2

2

您的问题是切片是引用类型,并且在多个 goroutine 中被访问。在perm中,您在a完成每个步骤的处理后直接打印。在permch中,您正在a通过通道发送,但随后立即开始再次修改它。由于通过通道发送的每个切片都引用相同的底层数组,因此您有一个竞争条件,即您的下一个循环迭代是否改变a或您Println()在 main 中的调用是否首先到达该数组。

一般来说,如果您在使用 goroutine 的任何程序中遇到意外行为,则可能存在竞争条件。运行带有-race标志的程序以查看位置。

编辑:另外,关闭频道对从频道读取的例程没有影响。可以继续读取通道,直到其缓冲区为空,此时它将开始返回该类型的零值。通道上的范围循环只会在通道关闭且缓冲区为空时终止。

于 2017-05-01T17:00:40.253 回答
0

它看起来像在打印它的同时permch修改,所以你的输出是乱码。amain

我可以想到三个简单的解决方法:

  1. a使用互斥锁保护访问。
  2. a在频道上放一个副本:
  3. 有某种来自 main 的返回信号,它已经打印并且permch可以继续。(真的不推荐这个,但它有效)。

2号很简单:

a2 := make([]int64, len(a))
copy(a2,a)
ch <- a2

这就是我推荐的。

对于 #1,只需将 a 声明var m sync.Mutex为包变量,并且Lock在您读取或修改a. 正如您所指出的,这是读者和下一个修改之间的竞争条件,所以它可能毕竟不是一个好的解决方案。

使用#3 在操场上的固定版本

于 2017-05-01T17:05:46.270 回答