I am trying to use channels to implement a kind of a worker pool. Please take a look at the code below
https://play.golang.org/p/g7aKxDoP9lf (The Go Playground)
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)
}
}
}
Here is a sample output
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
It seems that at the time when the pointer is recieved by worker2, the value has already changed in the original variable which is reflected in the value printed.
The question is how can this be avoided? How can this be worked around?
This problem is covered in the Channels section of Effective Go. Here's a short excerpt, with the variable name changed to match your code:
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.
It goes on to describe two solutions:
i
as an argument to the function in the goroutineSince your goroutine is started outside your loop, only #2 applies to your code.
The value that the received pointer points to is not what you expect because you're sending it a pointer to the same variable every time, so the worker sees whatever value that variable has at the time it dereferences the pointer. A typical way around this sort of problem is to make a copy of the variable inside the for
loop and send a pointer to that. That way, you're sending a pointer to a different object every time. Try this:
for i := 0; i < 10; i++ {
fmt.Printf("sending: %v\n", i)
iCopy := i
q2 <- &iCopy
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With