//…
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say(“world”)
say(“hello”)
}
with output:
world
hello
hello
…
Now, let’s comment out the call say(“hello”) and re-run. This time we get no output. The execution of the calling function main() is completed before the call to go-routine can execute.
So, let’s give the go-routine time to execute:
func main() {
go say(“world”)
//say(“hello”)
time.Sleep(3000 * time.Millisecond)
}
with output:
world
world
…
My question is, what would we do in a real-life situation to ensure that the say() go-routine execute as expected, not knowing in advance that it would execute quicker than main()? That is, to guarantee that the code in say() is executed.
If I understand your question correctly, the most common ways that you will probably see for goroutine synchronization is either by using sync.WaitGroup: https://golang.org/pkg/sync/#WaitGroup, or simply by using channels to communicate.
There are lots of ways to achieve this and I recommend you Google for goroutine synchronization.
Here’s a simple example using sync.WaitGroup:
package main
import (
"fmt"
"sync"
)
func sayHello(wg *sync.WaitGroup) {
defer wg.Done()
fmt.Println("hello")
}
func main() {
var wg sync.WaitGroup
wg.Add(3)
for i := 0; i < 3; i++ {
go sayHello(&wg)
}
// Wait for all goroutines to finish before
// continuing exectuion.
wg.Wait()
fmt.Println("continuing execution")
}
Edit: Here’s another simple example using channels to communicate.
package main
import "fmt"
func sayHello(done chan struct{}) {
fmt.Println("hello")
// Send on the done channel after saying hello.
done <- struct{}{}
}
func main() {
done := make(chan struct{})
for i := 0; i < 3; i++ {
go sayHello(done)
}
// Receive from the done channel 3 times
// before continuing execution.
for i := 0; i < 3; i++ {
<-done
}
fmt.Println("continuing execution")
}
Thanks for your replies. Let’s assume that function say() is from a third party and cannot be modified as you’ve shown. What do we do then? If I use the wait group as follows then I get a runtime error:
// say() remains unaltered
//...
func main() {
var wg sync.WaitGroup
wg.Add(1)
go say("world")
// Wait for all goroutines to finish before continuing exectuion.
wg.Wait()
fmt.Println("execution complete")
}
Well most functions can’t be altered, that’s why you would just wrap the function in a new function and call the function synchronously in an asynchronous wrapper that uses a WaitGroup, for example:
package main
import (
"fmt"
"sync"
)
// Pretending that say can't be altered.
func say(str string) {
fmt.Println(str)
}
// Create a separate function that will be called as a goroutine
// with the WaitGroup.
func sayHello(wg *sync.WaitGroup) {
defer wg.Done()
say("Hello")
}
func main() {
var wg sync.WaitGroup
wg.Add(3)
for i := 0; i < 3; i++ {
go sayHello(&wg)
}
// Wait for all goroutines to finish before
// continuing exectuion.
wg.Wait()
fmt.Println("continuing execution")
}
Normally, you would see this done like the following with a simple go func() { ... }() call wrapping the function you want to call asynchronously:
package main
import (
"fmt"
"sync"
)
// Pretending that say can't be altered.
func say(str string) {
fmt.Println(str)
}
func main() {
var wg sync.WaitGroup
wg.Add(3)
for i := 0; i < 3; i++ {
go func(wg *sync.WaitGroup) {
defer wg.Done() // Called only after `say()` is finished executing.
say("hello")
}(&wg)
}
// Wait for all goroutines to finish before
// continuing exectuion.
wg.Wait()
fmt.Println("continuing execution")
}
Indeed. In moth cases I’d simplify it somewhat by using a closure over the wait group, skipping the defer and doing the adding inside the loop (especially if you’re iterating over something with an unknown length, of course):
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go func() {
say("hello")
wg.Done()
}()
}
wg.Wait()
(There are valid reasons to do it otherwise, I’m not correcting @radovskyb just showing an alternative. :))
Was mainly just showing the use of defer incase Graham decided to use something like the following where without a deferred wg.Done() there’d be a deadlock when an error occured, but you’re right that I should have probably specified that defer definitely wasn’t needed for my simple say() function
go func() {
defer wg.Done()
if err := say("hello"); err != nil {
return
}
}()