我正在尝试在单个频道中实现队列,出队和重新排队。
我有两个问题:
为什么我会陷入僵局?我期待一个无限循环(因为我重新排队甚至生成更多元素的元素等等)。 range queue
总是不应该收听频道吗?
这是deadloks之前的印刷品的一部分:
enqueueing 1000
enqueueing 1001
dequeued 1001
dequeued 1001
using 1001
using 1001
两个不同的goroutines是否将相同的元素排队?我不明白为什么这个数据竞争;我认为range
每次都会选一个。
func main() {
queue := make(chan int)
start := 10
go func() { queue <- start }()
for element := range queue {
fmt.Println("dequeued ", element)
go enqueue(element, queue)
}
}
func enqueue(element int, queue chan int) {
fmt.Println("using ", element)
if element%2 == 0 {
fmt.Println("creating new elements from ", element)
var news = []int{element * 100, element*100 + 1}
for _, new := range news {
fmt.Println("enqueueing ", new)
go func() { queue <- new }()
}
}
}
答案 0 :(得分:4)
这是一个范围问题。开始使用go func() {...}
时会发生 lot ;它可能比并发的真正问题更常见。 the FAQ和the Go wiki中有关于它的部分。
您创建的匿名func
会在外部作用域中获取对变量的引用,而不会在func
时对其值进行引用声明运行。并且每次循环都会更新循环变量,因此它会在您的go
语句和实际运行的goroutine之间发生变化。
您可以通过在循环的每次迭代期间(即在for
括号内)声明另一个变量来解决此问题。如果您的循环为for i := range arr {...}
,则只需添加i := i
即可。所以,这个糟糕的版本:
arr := make([]int, 10)
for i := range arr {
go func() {
fmt.Println(i)
}()
}
... always prints 9。一个固定版本,在循环中重新声明i
:
arr := make([]int, 10)
for i := range arr {
i := i
go func() {
fmt.Println(i)
}()
}
... prints 0-9。重新宣布i
的一种不同的,可以说更优雅的方式是将其作为匿名func
的一个参数;那么这不是一个奇怪的独立声明:
arr := make([]int, 10)
for i := range arr {
go func(i int) {
fmt.Println(i)
}(i)
}
Here's that code。对于所有Playground版本,我必须添加同步,以便在goroutines运行之前main
不会退出。
令人困惑的是,每次通过循环“运行”一次的声明表现不同(范围显示的奇怪方式)但是绕过它的方法很简单。
在您的情况下:queue <- new
可以随时运行,并且在您完全通过for _, new
循环后运行结果。但是,它使用new
实际运行时的值,而不是执行go语句时的 。在这种情况下,你开始的两个goroutine都得到值1001,所以通过两个传递给enqueue的值的第二次是奇数(你可以在输出中看到两个using 1001
)因此没有任何写入队列,因此range queue
循环没有任何内容可供使用。频道也没有关闭,因此main
不能只是结束,所以你会陷入僵局。
您希望每次执行goroutine时“捕获”不同的值。为此,您可以将new := new
放在循环的顶部,这看起来很有趣。这足以使每个迭代的值与Go的视角“不同var
”,因此您可以在频道中插入1000
和1001
。
一旦你真正开始工作,Playground将无法成功运行你的代码,因为它永远循环并且有很多goroutines在运行,我猜Playground不喜欢它(过度使用资源)。如果您在退出前添加了100个元素的限制,则会获得http://play.golang.org/p/bBM3uTnvxi。你还会注意到它输出的数字很奇怪,因为每次乘以100会最终溢出机器的int类型,但这就是用低级语言编写程序的方式。
作为次要的事情,您可能不想命名变量new
,因为它也是内置函数的名称。这样做是合法的,只是令人困惑。 (命名长度变量len
可能有点反身,特别是如果你来自的语言没有任何问题。)
答案 1 :(得分:0)
为什么我会陷入僵局?我期待一个无限循环(因为我重新排队甚至生成更多元素的元素等等)。范围队列不应该总是在监听频道吗?
当通道上的范围循环到达通道缓冲区的末尾时(如果通道有一个),它将阻塞,等待通过通道发送更多元素。如果没有其他正在运行或可运行的goroutine,那么我们已陷入僵局。
最小例子:
http://play.golang.org/p/Vb4-RFEmm3
package main
func main() {
queue := make(chan int)
for _ = range queue {
}
}
两个不同的goroutine是否将相同的元素排队?
没有。发生了什么事情就是这一行(26):
go func() { queue <- new }()
指示Go的运行时安排新的goroutine,它不一定立即执行。当goroutine有效运行时,new
的值为1001,而不是打印的1000。