Gorountines.go question

The “A Tour of Go” contains the following example at [https://tour.golang.org/concurrency/1]:

//…
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.

Thanks
Graham

Hey @gmseed,

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")
}

And here’s another example that is more annotated: https://github.com/radovskyb/go-packages/blob/master/sync/waitgroup/main.go

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")
}
2 Likes

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")
}

Hey @gmseed,

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. :))

2 Likes

@calmh, I totally agree with that as well.

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 :stuck_out_tongue:

go func() {
	defer wg.Done()

	if err := say("hello"); err != nil {
		return
	}
}()
2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.