简体   繁体   中英

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

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
}

You have a data race. Your two goroutines are accessing shared variables without any explicit synchronization. You need to use a mutex in this case to ensure mutual exclusion as well as the visibility of changes made in one goroutine in others.

Declare a mutex:

myCharacter := new(Character)
mutex:=sync.Mutex{}

Use the mutex when you read/write the shared variables:

mutex.Lock()
myCharacter.STR = mySTR
myCharacter.DEX = myDEX
mutex.Unlock()

Also:

mutex.Lock()
fmt.Printf("STR: %d DEX: %d", myCharacter.STR, myCharacter.DEX)
mutex.Unlock()

Without mutex, there is no guarantee that modifications made to a shared variable are visible to other goroutines.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM