Maxim Yefremov
Maxim Yefremov

Reputation: 14165

golang functions: parallel execution with return

How to make two functions calls f1(2) and f1(1) execute in parallel so that all the program would execute for 2 seconds not for 3.

package main

import (
    "fmt"
    "time"
)

// sleeps for `secs` seconds
func f1(secs time.Duration) (result string) {
    fmt.Printf("waiting %V\n", secs)
    time.Sleep(secs * time.Second)
    result = fmt.Sprintf("waited for %d seconds", secs)
    return
}

// prints arg1, arg2
func f2(arg1, arg2 string) {
    fmt.Println(arg1)
    fmt.Println(arg2)
}

// this function executes for 3 seconds, because waits a lot
func runNotParallel() {

    out1 := f1(2)
    out2 := f1(1)
    f2(out1, out2)

}

// golang parallel return functions
// todo: make it run so all the function will executes for 2 seconds not for 3
func runParallel() {
    out1 := f1(2)
    out2 := f1(1)
    f2(out1, out2)
}

func main() {
    runNotParallel()
    runParallel()
}

playground

I guess I can do it only with channels. Should I redefine function f1 or I can leave it as is and change only way I call it?

Upvotes: 9

Views: 22628

Answers (4)

Clay
Clay

Reputation: 11605

With go 1.18 supporting generics, the channel solution can be made even more readable.

func async[T any](f func() T) chan T {
    ch := make(chan T)
    go func() {
        ch <- f()
    }()
    return ch
}

func main() {
    startTime := time.Now().Local()

    out1 := async(func() string {
        time.Sleep(1 * time.Second)
        return "thing 1"
    })
    out2 := async(func() string {
        time.Sleep(2 * time.Second)
        return "thing 2"
    })

    results := []string{<-out1, <-out2}

    fmt.Printf("results: %v\n", results)
    fmt.Printf("took %v", time.Since(startTime))
}

playground

The lo package provides this along with lots of other generic helper functions.

Upvotes: 0

Oleg Kokorin
Oleg Kokorin

Reputation: 2672

here is a solution without channels but with the missing f2 synchronization:

package main

import (
    "fmt"
    "sync"
    "time"
)

// sleeps for `secs` seconds
func f1(secs time.Duration, result *string, sg *sync.WaitGroup) () {
    fmt.Printf("waiting %v\n", secs)
    time.Sleep(secs * time.Second)
    *result = fmt.Sprintf("waited for %d seconds", secs)
    if sg!= nil {
        sg.Done()
    }
    return
}

// prints arg1, arg2
func f2(arg1, arg2 string) {
    fmt.Println(arg1)
    fmt.Println(arg2)
}

// this function executes for 3 seconds, because waits a lot
func runNotParallel() {

    var out1, out2 string
    f1(2, &out1, nil)
    f1(1, &out2,nil)
    f2(out1, out2)

}

// golang parallel return functions
// todo: make it run so all the function will executes for 2 seconds not for 3
func runParallel() {
    var sg sync.WaitGroup
    sg.Add(2)
    var out1, out2 string
    go f1(2, &out1, &sg)
    go f1(1, &out2, &sg)
    sg.Wait()
    f2(out1, out2)
}

func main() {
    runNotParallel()
    runParallel()
}

basically, go operator blocks from using/accessing a return values but it could be done using a pointers for the return place holders in the signature

Upvotes: 1

Raed Shomali
Raed Shomali

Reputation: 1455

Another way you could do it is using WaitGroup

I wrote this utility function to help parallelize a group of functions:

import "sync"

// Parallelize parallelizes the function calls
func Parallelize(functions ...func()) {
    var waitGroup sync.WaitGroup
    waitGroup.Add(len(functions))

    defer waitGroup.Wait()

    for _, function := range functions {
        go func(copy func()) {
            defer waitGroup.Done()
            copy()
        }(function)
    }
}

So in your case, we could do this

value1 := ""
value2 := ""

func1 := func() {
    value1 = f1(2)
}

func2 = func() {
    value2 = f1(1)
}

Parallelize(func1, func2)

f2(out1, out2)

If you wanted to use the Parallelize function, you can find it here https://github.com/shomali11/util

Upvotes: 9

mattn
mattn

Reputation: 7723

Use chan/goroutine

package main

import (
    "fmt"
    "time"
)

// sleeps for `secs` seconds
func f1(secs time.Duration) (result string) {
    fmt.Printf("waiting %v\n", secs)
    time.Sleep(secs * time.Second)
    result = fmt.Sprintf("waited for %v seconds", secs)
    return
}

// prints arg1, arg2
func f2(arg1, arg2 string) {
    fmt.Println(arg1)
    fmt.Println(arg2)
}

// this function executes for 3 seconds, because waits a lot
func runNotParallel() {
    out1 := f1(2)
    out2 := f1(1)
    f2(out1, out2)

}

// golang parallel return functions
// todo: make it run so all the function will executes for 2 seconds not for 3
func runParallel() {
    out1 := make(chan string)
    out2 := make(chan string)
    go func() {
        out1 <- f1(2)
    }()
    go func() {
        out2 <- f1(1)
    }()
    f2(<-out1, <-out2)
}

func main() {
    runNotParallel()
    runParallel()
}

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

Upvotes: 17

Related Questions