Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use a method as a goroutine function

Tags:

go

goroutine

I have this code. I expect to output:

hello : 1 
world : 2

but it outputs:

world : 2
world : 2

Is there something wrong with my code?

package main

import (
    "fmt"
    "time"
)

type Task struct {
    name string
    data int32
}

func (this *Task) PrintData() {
    fmt.Println(this.name, ":", this.data)
}

func main() {
    tasks := []Task{{"hello", 1}, {"world", 2}}
    for _, task := range tasks {
        go task.PrintData()
    }
    time.Sleep(time.Second * 5000)
}
like image 878
Devin Avatar asked Mar 21 '16 01:03

Devin


People also ask

How do you make a Goroutine?

Prefix the function or method call with the keyword go and you will have a new Goroutine running concurrently. In line no. 11, go hello() starts a new Goroutine. Now the hello() function will run concurrently along with the main() function.

What is a Goroutine function?

A goroutine is a function that executes simultaneously with other goroutines in a program and are lightweight threads managed by Go. A goroutine takes about 2kB of stack space to initialize.

Is main function a Goroutine?

Yes, the main function runs as a goroutine (the main one). A goroutine is a lightweight thread managed by the Go runtime. go f(x, y, z) starts a new goroutine running f(x, y, z) The evaluation of f, x, y, and z happens in the current goroutine and the execution of f happens in the new goroutine.

How do you get data from a Goroutine?

You only need one extera receiving goroutine e.g. getData and then the main goroutine will send the data as it arrives using a channel named ch , and you need one buffered channel for signalling e.g. batchCompleted , and a WaitGroup to wait for the getData synchronization, when it is done. Splendid answer.


2 Answers

Because PrintData is a pointer receiver and task is a value, the compiler automatically takes the address of task when making the method call. The resulting call is the same as (&task).PrintData().

The variable task is set to a different value on each iteration through the loop. The first goroutine doesn't run until task is set to second value. Run this example to see that the same address is passed to PrintData on each iteration.

There are a few ways to fix this. The first is to use *Task in the slice:

tasks := []*Task{{"hello", 1}, {"world", 2}}
for _, task := range tasks {
    go task.PrintData()
}

playground example

The second is to create a new variable inside the loop:

tasks := []Task{{"hello", 1}, {"world", 2}}
for _, task := range tasks {
    task := task
    go task.PrintData()
}

playground example

A third is to take the address of the slice element (using the automatically inserted address operation):

tasks := []Task{{"hello", 1}, {"world", 2}}
for i := range tasks {
    go tasks[i].PrintData()
}

playground example

Yet another option is to change PrintData to a value receiver to prevent the method call from automatically taking the address of task:

func (this Task) PrintData() {
    fmt.Println(this.name, ":", this.data)
}

playground example

This issue is similar to the issue discussed in the closures and goroutines FAQ. The difference between the issues is the mechanism used to pass a pointer to the goroutine function. The code in the question uses the method's receiver argument. The code in the FAQ uses a closure.

like image 73
Bayta Darell Avatar answered Sep 20 '22 02:09

Bayta Darell


Go Frequently Asked Questions (FAQ)

What happens with closures running as goroutines?

Some confusion may arise when using closures with concurrency. Consider the following program:

func main() {
    done := make(chan bool)

    values := []string{"a", "b", "c"}
    for _, v := range values {
        go func() {
            fmt.Println(v)
            done <- true
        }()
    }

    // wait for all goroutines to complete before exiting
    for _ = range values {
        <-done
    }
}

One might mistakenly expect to see a, b, c as the output. What you'll probably see instead is c, c, c. This is because each iteration of the loop uses the same instance of the variable v, so each closure shares that single variable. When the closure runs, it prints the value of v at the time fmt.Println is executed, but v may have been modified since the goroutine was launched. To help detect this and other problems before they happen, run go vet.

To bind the current value of v to each closure as it is launched, one must modify the inner loop to create a new variable each iteration. One way is to pass the variable as an argument to the closure:

for _, v := range values {
    go func(u string) {
        fmt.Println(u)
        done <- true
    }(v)
}

In this example, the value of v is passed as an argument to the anonymous function. That value is then accessible inside the function as the variable u.

Even easier is just to create a new variable, using a declaration style that may seem odd but works fine in Go:

for _, v := range values {
    v := v // create a new 'v'.
    go func() {
        fmt.Println(v)
        done <- true
    }()
}

Just create a new variable for the closure using a declaration style that may seem odd but works fine in Go. Add task := task. For example,

package main

import (
    "fmt"
    "time"
)

type Task struct {
    name string
    data int32
}

func (this *Task) PrintData() {
    fmt.Println(this.name, ":", this.data)
}

func main() {
    tasks := []Task{{"hello", 1}, {"world", 2}}
    for _, task := range tasks {
        task := task
        go task.PrintData()
    }
    time.Sleep(time.Second * 5000)
}

Output:

hello : 1
world : 2
like image 34
peterSO Avatar answered Sep 22 '22 02:09

peterSO