Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sending pointers over a channel

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?

like image 395
Omer Akhter Avatar asked Dec 13 '22 17:12

Omer Akhter


2 Answers

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 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.

It goes on to describe two solutions:

  1. Pass the value of i as an argument to the function in the goroutine
  2. Create a new variable in the loop and use that instead

Since your goroutine is started outside your loop, only #2 applies to your code.

like image 121
jrefior Avatar answered Jan 11 '23 16:01

jrefior


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
}
like image 28
Andy Schweig Avatar answered Jan 11 '23 15:01

Andy Schweig