3

I'm going through 'A Tour of Go' and have been editing most of the lessons to make sure I fully understand them. I have a question regarding: https://tour.golang.org/concurrency/1

package main

import (
    "fmt"
    "time"
)

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

Leaving main the way it is produces a random ordering of hellos and worlds because the threads are executing in different orders each time the program runs. I have two questions:

  1. If I remove go from the line with world and add it to the line with hello, world is printed 5 times and hello is not printed at all. Why is that?
  2. If I add go in front of both lines, nothing is printed at all. Why is that?

I have some experience with concurrency using C++ (although it was a while ago) and some more recent experience with Python, but would describe my overall experience with concurrency fairly novice-level.

Thanks!

leif
  • 45
  • 3

2 Answers2

6

The program terminates before you get a chance to see the results.

You can fix this by adding a statement that ensures main doesn't exit before the other routines are finished.


From Go - Concurrency:

With a goroutine we return immediately to the next line and don't wait for the function to complete.

They give a code example:

package main

import "fmt"

func f(n int) {
  for i := 0; i < 10; i++ {
    fmt.Println(n, ":", i)
  }
}

func main() {
  go f(0)
  var input string
  fmt.Scanln(&input)
}

In regards to the code above:

This is why the call to the Scanln function has been included; without it the program would exit before being given the opportunity to print all the numbers.

Vince
  • 14,470
  • 7
  • 39
  • 84
  • Thanks for the answer and accompanying literature! What is the preferred way to wait for go routines to finish? Or if you find yourself in that situation does it mean that you should be rearchitecting your code to avoid that? I also posted another question from the exercises if you're feeling generous :) https://stackoverflow.com/questions/46147914/explanation-of-channels-from-tour-of-go-webcrawler-exercise – leif Sep 11 '17 at 03:49
  • @leif From my experiences, theres usually no need to redesign to avoid this, though that's just personal experience. Generally, your application would consist of routines that handle tasks and delegate results back to a thread which runs until the application actually requires termination. If you *really* need this behavior, you could use [`Gosched`](https://golang.org/pkg/runtime/#Gosched) to yield the main thread, allowing your routines to process. Check out [this post](https://stackoverflow.com/questions/13107958/what-exactly-does-runtime-gosched-do). – Vince Sep 11 '17 at 04:03
0

If I remove the go keyword from the line say("world") and add it to the line say("hello"), world is printed 5 times and hello is not printed at all. Why is that?

If I add the go in front of both lines, nothing is printed at all. Why is that?

In both cases the same problem occurs, you are executing unsynchronized operations. It results in your program returning from main before all started work was processed. The specification states that When that [main] function invocation returns, the program exits. It does not wait for other (non-main) goroutines to complete.

When you use the go keyword, the runtime starts the execution of a function call as an independent concurrent thread of control. You need to use the synchronization language primitives to re synchronize the exit order of operations following the call to main with the remaining asynchronous jobs.

The language offers channels or otherwise WaitGroups via the sync package to help you implement that behavior.

For example

package main

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

var wg sync.WaitGroup

func say(s string) {
    for i := 0; i < 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
        wg.Done()
    }
}

func main() {
    wg.Add(5)
    say("world")
    wg.Add(5)
    say("hello")

    wg.Wait()
}