Why do my struct values not update in the following case when using two goroutines?

Issue

So, I’m very new to Go, started learning the language last weekend and created a basic character generator / autoroller for an RPG.
Since the generator does a lot of random number rolling until certain values are reached, I thought I might use goroutines to speed up processing.
This is my best effort so far but somehow during the final Printf I always get "STR: 0 DEX: 0" even though I can see during debugging that the two parallel functions are setting the values correctly.
The myCharacter struct is declared outside of them so I thought updating it should work fine?

This is my awful code. What I’m trying to achieve is roll for "STR" and "DEX" in parallel goroutines until one of them reaches an arbitrary condition (here a value of 1000000 for both just as a test).

Anybody can help me out why myCharacter.STR and myCharacter.DEX print out as 0 at the end?

package main

import (
    "fmt"
    "math/rand"
    "time"
)

type Character struct {
    STR int
    DEX int
}

func main() {
    rand.Seed(time.Now().UTC().UnixNano())
    QuitChan := make(chan bool)

    myCharacter := new(Character)
    go func() {
        for {
            select {
            case <-QuitChan:
                return
            default:
                mySTR, myDEX := RollChar()
                if mySTR >= 1000000 && myDEX >= 1000000 {
                    myCharacter.STR = mySTR
                    myCharacter.DEX = myDEX
                    QuitChan <- true
                    return
                }
            }
        }
    }()
    go func() {
        for {
            select {
            case <-QuitChan:
                return
            default:
                mySTR, myDEX := RollChar()
                if mySTR >= 1000000 && myDEX >= 1000000 {
                    myCharacter.STR = mySTR
                    myCharacter.DEX = myDEX
                    QuitChan <- true
                    return
                }
            }
        }
    }()
    fmt.Printf("STR: %d DEX: %d", myCharacter.STR, myCharacter.DEX)
}

func RollChar() (int, int) {
    mySTR := rand.Intn(1000000) + 1
    myDEX := rand.Intn(1000000) + 1
    return mySTR, myDEX
}

Solution

Your goroutines never run. main does not wait for goroutines to complete, it prints and quits before they execute.

This can be solved with WaitGroups to make main wait until all goroutines are done. Its basically a fancy counter.

    // Create the WaitGroup
    var wg sync.WaitGroup

    // Add one thing to wait for.
    // This must be done outside the goroutine to ensure it's added
    // before `wg.Wait()` is called.
    wg.Add(1)
    go func() {
        # When the goroutine exits, say this one thing is done.
        defer wg.Done()
        for {
            ...
        }
    }()

    // Same for the next one.
    wg.Add(1)
    go func() {
        defer wg.Done()
        for {
            ...
        }
    }()

    // Wait until the two things in the WaitGroup are done.
    wg.Wait()
    
    fmt.Printf("STR: %d DEX: %d", myCharacter.STR, myCharacter.DEX)

That will get your goroutines running. Next is to not cut & paste the code, and use a loop.

    for i:= 0; i < 2; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for {
                myCharacter.STR = 42;
                myCharacter.DEX = 23;
                break;
            }
        }()
    }

Note that if you expect both values to be 1,000,000 that will take 1,000,000,000,000 tries (1 trillion). Two goroutines will not make that much faster.

Answered By – Schwern

Answer Checked By – Willingham (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.