In this tutorial, we will try to catch return values from goroutines. Fetching the return value from a function and running a goroutine (asynchronously) are essentially incompatible activities. We use the goroutines when we want to run them separately from the main goroutine. But when you assign the return value from a function to a variable you are expecting to have this value within the variable. It means you have to wait for the function to return the value and then continue the next steps in the main function.
Channels are the most intuitive way to retrieve a value from a goroutine. Channels are the pipes that link running goroutines. One goroutine can transmit values through channels, while another goroutine or synchronous function can receive those values.
Example 1: Using channel to fetch return value from goroutines
Fetch single value from goroutine
In this example we will use golang channel to fetch the goroutine return value:
package main
import "fmt"
func main() {
result := make(chan int, 1)
go multiply(8, 9, result)
// catching return
value := <-result
fmt.Printf("Returned Value from goroutine: %d\n", value)
close(result)
}
func multiply(a, b int, result chan int) {
mul := a * b
// send the result to channel
result <- mul
}
Output:
Returned Value from goroutine: 72
In the main function we will wait for the channel to receive the return value from the goroutine:
value := <-result
This line will wait until a value is added to the channel. So even if we add a time.Sleep
to the goroutine, our main goroutine still hangs until the channel receives the returned value from goroutine and the output will be the same as the below example:
Fetch multiple return values from goroutines
You can retrieve a goroutine's return value using channels. Goroutine synchronization and communication are provided by channels. To receive multiple return values from a goroutine, we can create a struct whose fields are the returned value, then create a channel of that type. Let’s see an example of fetching multiple return values from a goroutine using a channel:
package main
import (
"fmt"
"time"
)
type Result struct {
Sum int
Multiply int
Subtract int
}
func main() {
result := make(chan Result, 1)
go operation(8, 9, result)
// catching return struct from channel
value := <-result
fmt.Printf("Retured struct: %+v\n", value)
close(result)
}
func operation(a, b int, result chan Result) {
mul := a * b
sum := a + b
sub := a - b
returnValues := Result{Sum: sum, Multiply: mul, Subtract: sub}
// set time.Sleep for goroutine
time.Sleep(time.Second * 3)
// return a struct
result <- returnValues
}
Output:
Retured struct: {Sum:17 Multiply:72 Subtract:-1}
Example 2: Directly assign return value from goroutine to a variable
The example below shows how we can directly assign the return value from a goroutine to a variable:
package main
import (
"fmt"
"time"
)
func main() {
var mul int
go func() {
mul = 8 * 9
}()
// sleep a little bit for goroutine to update the variable
time.Sleep(time.Second * 3)
fmt.Println("Returned value from goroutine:", mul)
}
Output:
Returned value from goroutine: 72
We can see that we can update the variable inside the goroutine. But we have to do a trick: time.Sleep()
to wait for the goroutine to change the variable. So the problem with this method is how are you going to use the variable from the original goroutine? We have to make sure the goroutine has already updated the value before using it. We can use a WaitGroup to ensure the goroutine has done its job but it does not make sense. We better use a channel to get the return value from a goroutine.
Summary
After trying some examples of how to catch the return values from goroutines, we can see that using the channel is the best practice way. We also can directly update the variable inside the goroutine but it is very hard to keep track of when the variable is updated.
References
https://go.dev/tour/concurrency/1
https://pkg.go.dev/sync
Catching return values from goroutines - Stack Overflow