Search code examples
gogoroutine

Inconsistent results using goroutines with sync.WaitGroup


I am trying to count the number of prime numbers that are smaller than an arbitrary integer i using goroutines (in Go lang). For example, if i is 100, the result should be 25.

The following is my current implementation:

package "main"

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

var wg sync.WaitGroup

func isprime(x int) bool {
    if x == 2 {
        return true
    }
    if x == 1 || x%2 == 0 {
        return false
    }
    var xi = float64(x)
    for i := 3; float64(i) < (math.Pow(xi, 0.5) + 1.0); i += 2.0 {
        if x%i == 0 {
            return false
        }
    }
    return true
}

func main() {
    fmt.Print("Till what number should I count primes? ")
    var i int
    fmt.Scan(&i)

    r := 0
    pr := &r
    fmt.Println("Counting primes till ", i)
    start := time.Now()
    for x := 0; x <= i; x++ {
        wg.Add(1)
        go func(n int) {
            defer wg.Done()
            if isprime(n) {
                *pr += 1
            }
        }(x)
    }
    wg.Wait()
    elapsed := time.Since(start).Seconds()
    fmt.Println("Counted", r, "primes")
    fmt.Println("took", elapsed, "seconds")
}

When I run this program, I get the correct results for smaller values of i (till about a 1000)

But for larger values of i, the results are inconsistent and incorrect.

❯ ./main
Till what number should I count primes? 10000
Counting primes till  10000
Counted 1228 primes
took 0.006776541 seconds
❯ ./main
Till what number should I count primes? 10000
Counting primes till  10000
Counted 1227 primes
took 0.004183875 seconds
❯ ./main
Till what number should I count primes? 1000000
Counting primes till  1000000
Counted 78254 primes
took 0.441985921 seconds
❯ ./main
Till what number should I count primes? 1000000
Counting primes till  1000000
Counted 78327 primes
took 0.430042047 seconds

The fluctuation in results increases as the value of i gets larger. What is causing this? Is there any way I can make it consistent and correct?


Solution

  • You have a shared variable without proper synchronization. There is a race condition (*pr += 1). Adding mutex before and after shared variable fix it (mu.Lock(), mu.Unlock()).

    Code:

    var wg sync.WaitGroup
    var mu sync.Mutex
    
    func main() {
        fmt.Print("Till what number should I count primes? ")
        var i int
        fmt.Scan(&i)
    
        r := 0
        pr := &r
        fmt.Println("Counting primes till ", i)
        start := time.Now()
        for x := 0; x <= i; x++ {
            wg.Add(1)
            go func(n int) {
                defer wg.Done()
                if isprime(n) {
                    mu.Lock()   // <= lock
                    *pr += 1
                    mu.Unlock()  // <= unlock
                }
            }(x)
        }
        wg.Wait()
        elapsed := time.Since(start).Seconds()
        fmt.Println("Counted", r, "primes")
        fmt.Println("took", elapsed, "seconds")
    }
    

    Output:

    Till what number should I count primes? 1000000
    Counting primes till  1000000
    Counted 78498 primes
    took 0.6783484 seconds
    Till what number should I count primes? 1000000
    Counting primes till  1000000
    Counted 78498 primes
    took 0.5428273 seconds
    Till what number should I count primes? 1000000
    Counting primes till  1000000
    Counted 78498 primes
    took 0.5521617 seconds