通过通道发送指针
Sending pointers over a channel
我正在尝试使用通道来实现一种工作池。请看下面的代码
https://play.golang.org/p/g7aKxDoP9lf(围棋游乐场)
package main
import (
"fmt"
"time"
)
func main() {
q1 := make(chan int)
fmt.Printf("worker 1\n")
go worker1(q1)
for i := 0; i < 10; i++ {
fmt.Printf("sending: %v\n", i)
q1 <- i
}
time.Sleep(time.Second)
fmt.Printf("\n\nworker 2\n")
q2 := make(chan *int)
go worker2(q2)
for i := 0; i < 10; i++ {
fmt.Printf("sending: %v\n", i)
q2 <- &i
}
time.Sleep(time.Second)
}
func worker1(qTodo <-chan int) {
var curr int
for {
select {
case curr = <-qTodo:
fmt.Printf("got: %v\n", curr)
}
}
}
func worker2(qTodo <-chan *int) {
var curr *int
for {
select {
case curr = <-qTodo:
fmt.Printf("got: %v\n", *curr)
}
}
}
这是一个示例输出
worker 1
sending: 0
got: 0
sending: 1
sending: 2
got: 1
got: 2
sending: 3
sending: 4
got: 3
got: 4
sending: 5
sending: 6
got: 5
got: 6
sending: 7
sending: 8
got: 7
got: 8
sending: 9
got: 9
worker 2
sending: 0
got: 0
sending: 1
sending: 2
got: 2
got: 2
sending: 3
sending: 4
got: 4
got: 4
sending: 5
sending: 6
got: 6
got: 6
sending: 7
sending: 8
got: 8
got: 8
sending: 9
got: 10
似乎在worker2接收到指针的时候,原来的变量中的值已经改变,反映在打印的值中。
问题是如何避免这种情况?如何解决这个问题?
接收到的指针指向的值不是您所期望的,因为您每次都向它发送指向同一变量的指针,因此工作人员在取消引用指针时会看到该变量具有的任何值。解决此类问题的典型方法是在 for
循环内复制变量并发送指向该变量的指针。这样,您每次都发送指向不同对象的指针。试试这个:
for i := 0; i < 10; i++ {
fmt.Printf("sending: %v\n", i)
iCopy := i
q2 <- &iCopy
}
此问题已在 the Channels section of Effective Go 中解决。这是一个简短的摘录,变量名已更改以匹配您的代码:
The bug is that in a Go for
loop, the loop variable is reused for each iteration, so the i
variable is shared across all goroutines. That's not what we want. We need to make sure that i
is unique for each goroutine.
它接着描述了两个解决方案:
- 将
i
的值作为参数传递给goroutine中的函数
- 在循环中创建一个新变量并改用它
由于您的 goroutine 是在您的循环之外启动的,因此只有 #2 适用于您的代码。
我正在尝试使用通道来实现一种工作池。请看下面的代码
https://play.golang.org/p/g7aKxDoP9lf(围棋游乐场)
package main
import (
"fmt"
"time"
)
func main() {
q1 := make(chan int)
fmt.Printf("worker 1\n")
go worker1(q1)
for i := 0; i < 10; i++ {
fmt.Printf("sending: %v\n", i)
q1 <- i
}
time.Sleep(time.Second)
fmt.Printf("\n\nworker 2\n")
q2 := make(chan *int)
go worker2(q2)
for i := 0; i < 10; i++ {
fmt.Printf("sending: %v\n", i)
q2 <- &i
}
time.Sleep(time.Second)
}
func worker1(qTodo <-chan int) {
var curr int
for {
select {
case curr = <-qTodo:
fmt.Printf("got: %v\n", curr)
}
}
}
func worker2(qTodo <-chan *int) {
var curr *int
for {
select {
case curr = <-qTodo:
fmt.Printf("got: %v\n", *curr)
}
}
}
这是一个示例输出
worker 1
sending: 0
got: 0
sending: 1
sending: 2
got: 1
got: 2
sending: 3
sending: 4
got: 3
got: 4
sending: 5
sending: 6
got: 5
got: 6
sending: 7
sending: 8
got: 7
got: 8
sending: 9
got: 9
worker 2
sending: 0
got: 0
sending: 1
sending: 2
got: 2
got: 2
sending: 3
sending: 4
got: 4
got: 4
sending: 5
sending: 6
got: 6
got: 6
sending: 7
sending: 8
got: 8
got: 8
sending: 9
got: 10
似乎在worker2接收到指针的时候,原来的变量中的值已经改变,反映在打印的值中。
问题是如何避免这种情况?如何解决这个问题?
接收到的指针指向的值不是您所期望的,因为您每次都向它发送指向同一变量的指针,因此工作人员在取消引用指针时会看到该变量具有的任何值。解决此类问题的典型方法是在 for
循环内复制变量并发送指向该变量的指针。这样,您每次都发送指向不同对象的指针。试试这个:
for i := 0; i < 10; i++ {
fmt.Printf("sending: %v\n", i)
iCopy := i
q2 <- &iCopy
}
此问题已在 the Channels section of Effective Go 中解决。这是一个简短的摘录,变量名已更改以匹配您的代码:
The bug is that in a Go
for
loop, the loop variable is reused for each iteration, so thei
variable is shared across all goroutines. That's not what we want. We need to make sure thati
is unique for each goroutine.
它接着描述了两个解决方案:
- 将
i
的值作为参数传递给goroutine中的函数 - 在循环中创建一个新变量并改用它
由于您的 goroutine 是在您的循环之外启动的,因此只有 #2 适用于您的代码。