英文:
Golang range through channel with odd behaviour when inplementing Heap's permutation algorithm
问题
我正在为你翻译以下内容:
我试图使用通道在Go语言中实现Heap's算法。下面的代码在仅仅将切片打印到屏幕上时工作正常,但是当使用通道将数组传递给主函数中的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)
}
}
你可以尝试将通道的关闭操作放在主函数中的for循环之后,看看是否能解决重复打印的问题。
英文:
I was trying to implement Heap's Algorithm in go using channels. The code below is working fine when just printing the slices on the screen, but when using channels to delivery the arrays to a for/range loop on main function some unexpected behaviour occurs and the slices/arrays are printed in duplicity and not all permutations are sent. I thought that maybe i'm closing the channel earlier than the main function is able to print the results but i wouldn't expect that double print. Why is this happening and how can i make it work.
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)
}
}
答案1
得分: 2
你的问题在于切片是引用类型,并且在多个 goroutine 中被访问。在 perm
函数中,你在每一步完成处理后直接打印 a
。在 permch
函数中,你将 a
发送到通道中,但立即开始修改它。由于每个发送到通道的切片都引用相同的底层数组,所以在下一次循环迭代修改 a
或者主函数中的 Println()
调用先访问该数组之间存在竞争条件。
一般来说,如果在使用 goroutine 的任何程序中遇到意外行为,很可能是存在竞争条件。可以使用 -race
标志运行程序以查看问题出在哪里。
编辑: 另外,关闭一个通道对于从通道中读取的例程没有影响。通道可以继续读取,直到其缓冲区为空,此时它将开始返回该类型的零值。使用 range 循环遍历通道只有在通道关闭且其缓冲区为空时才会终止。
英文:
Your problem is that slices are reference types, and are being accessed in multiple goroutines. In perm
, you're printing a
directly as you finish processing it at each step. In permch
, you're sending a
over the channel, but then immediate starting to modify it again. Since each slice sent over the channel refers to the same underlying array, you have a race condition as to whether your next loop iteration alters a
or your Println()
call in main gets to that array first.
In general, if you're running into unexpected behavior in any program using goroutines, you probably have a race condition. Run the program with the -race
flag to see where.
Edit: also, closing a channel has no effect on a routine reading from the channel. A channel can continue to be read until its buffer is empty, at which point it will start returning the zero value for that type instead. Range loops over a channel will only terminate once the channel is closed and its buffer is empty.
答案2
得分: 0
看起来permch
在main
打印a
的同时修改了它,所以输出结果混乱。
我可以想到三种简单的修复方法:
- 使用互斥锁保护对
a
的访问。 - 在通道上放置
a
的副本: - 从主函数返回一个信号,表示已经打印完毕,
permch
可以继续执行。(不太推荐,但是可以工作)
第二种方法非常简单:
a2 := make([]int64, len(a))
copy(a2, a)
ch <- a2
这是我推荐的方法。
对于第一种方法,只需在包级别声明一个var m sync.Mutex
,并在读取或修改a
时进行锁定。不过,正如你指出的,这会导致读取者和下一次修改之间的竞争条件,所以这可能并不是一个好的解决方案。
英文:
It looks like permch
is modifying a
at the same time as main
is printing it, so your output is garbled.
I can think of three easy fixes:
- Guard access to
a
with a mutex. - Put a copy of
a
on the channel: - Have some kind of return signal from main that it has printed and
permch
can continue. (don't really recommend this, but it works).
Number 2 is pretty simple:
a2 := make([]int64, len(a))
copy(a2,a)
ch <- a2
and is what I would recommend.
For #1, just declare a var m sync.Mutex
as a package variable and Lock
is anytime you read or modify a
. This is a race condition though between the reader and the next modification, as you pointed out, so it probably isn't a good solution after all.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论