6
\$\begingroup\$

I have two goroutines that each need to return their results to the main function. These results are of different types.

I have implemented a solution that uses a structure that holds these results. The structure is passed to the routines and each routine knows which field in the structure to use to save the results.

I am new to Golang and I am not sure if this is an elegant solution. Maybe channels are more suitable?

package main

import (
    "fmt"
    "sync"
)

type Results struct {
    x float64
    y int
}

func main() {
    var r Results
    var wg sync.WaitGroup

    wg.Add(2)

    go func(r *Results, wg *sync.WaitGroup) {
        defer wg.Done()
        r.x = 1.2
    }(&r, &wg)

    go func(r *Results, wg *sync.WaitGroup) {
        defer wg.Done()
        r.y = 34
    }(&r, &wg)

    wg.Wait()
    fmt.Println(r)
}

https://play.golang.org/p/XW1LccJvAn

\$\endgroup\$
  • \$\begingroup\$ If you were to do it this way (see my answer below), I wouldn't bother passing anything around at all. Just have the closures use wg directly and write to x and y (which you'd just declare as locals first). E.g. play.golang.org/p/dMDm6dWlOq \$\endgroup\$ – Dave C Feb 13 '15 at 22:05
4
\$\begingroup\$

Using a structure and waitgroups is silly for something this simple. Just make two channels and fire off two goroutines. You don't care which one finishes first just read from both channels in whatever order is convenient (or even do something like someOtherFunction(<-c1, <-c2)).

E.g.:

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    c1 := make(chan float64, 1)
    c2 := make(chan int, 1)

    go func() {
        // simulate spending time to do work to get answer
        time.Sleep(time.Duration(rand.Intn(2000)) * time.Millisecond)
        c1 <- 1.2
    }()

    go func() {
        time.Sleep(time.Duration(rand.Intn(2000)) * time.Millisecond)
        c2 <- 34
    }()

    x := <-c1
    y := <-c2
    fmt.Println(x, y)
}

https://play.golang.org/p/Qoh5IvFROo

\$\endgroup\$
  • \$\begingroup\$ you seem to imply that structures and a wait group are more "heavy" than channels. as a beginner I thought that this is the other way around. would you please elaborate more about this? why do you think so? \$\endgroup\$ – akonsu Feb 13 '15 at 22:36
  • \$\begingroup\$ It's not about time/overhead, it's about simplicity. I hope no one would bother to even do such a thing unless one or both of the tasks was time consuming in which case any minor overhead of one synchronizing method versus another is complete noise compared to the relative complexity of the resulting code. Simple is better. Here a pair of channels is the simplest thing to do and for this task perform more than adequately. \$\endgroup\$ – Dave C Feb 18 '15 at 11:11
2
\$\begingroup\$

The only thing that you have to watch out for is that it would be easy for a give goroutine to access the wrong data field, and in a larger context this may be hard to catch. So for bigger, more complexed cases, I would suggest having a channel for each type of data that you want the goroutines to handle, and then passing the result back on a channel. If you've handled your types well, this will give you more robust code.

However, given the limited scope and low number of goroutines in this example, I think that this is an elegant way of handling the situation. Yes, you could have used channels, but in this case it would add complexity and get you very little. Nice clean code.

\$\endgroup\$
  • \$\begingroup\$ I disagree. Adding a struct and waitgroups and complicating to goroutine functions is in no way simpler or cleaner than just using a pair of channels. At minimum the structure and passing of r and wg is pointless. \$\endgroup\$ – Dave C Feb 13 '15 at 22:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.