当使用Heap的置换算法时,Golang通过具有奇怪行为的通道进行调整

时间:2017-05-01 16:24:49

标签: go range permutation slice channels

我试图在使用频道时实施here。只是在屏幕上打印切片时,下面的代码工作正常,但是当使用通道将数组传递到主函数上的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)
    }

}

2 个答案:

答案 0 :(得分:2)

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

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

编辑,关闭频道也不会影响该频道的常规阅读。可以继续读取通道,直到其缓冲区为空,此时它将开始返回该类型的零值。通道上的范围循环只有在通道关闭后才会终止并且其缓冲区为空

答案 1 :(得分:0)

看起来permch正在修改a main正在打印它,因此您的输出会出现乱码。

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

  1. 使用互斥锁保护对a的访问权限。
  2. 在频道上放置a的副本:
  3. 从主要打印出来的某种返回信号,permch可以继续。 (不要真的推荐这个,但它确实有用)。
  4. 2号非常简单:

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

    是我的推荐。

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

    fixed version on playground using #3