Goroutine с sync.waitGroup каждый раз выводит разные значения

Код ниже будет печатать разные значения каждый раз после выполнения, но я хочу, чтобы значения были одинаковыми, как изменить приведенный ниже код без использования time.Sleep

package main

import (
    "fmt"
    "sync"
)

var total int
var wg sync.WaitGroup

// Inc increments the counter for the given key.
func inc(num int) {
    total += num
    wg.Done()
}

// Value returns the current value of the counter for the given key.
func getValue() int {
    return total
}

func main() {
    for i := 1; i <= 1000; i++ {
        wg.Add(1)
        go inc(i)
    }
    wg.Wait()
    fmt.Println(getValue())
}


person Eason    schedule 20.08.2020    source источник


Ответы (3)


У вас гонка за данными, и результат не определен. Вам необходимо синхронизировать доступ к переменной share:

var total int
var lock sync.Mutex
var wg sync.WaitGroup

// Inc increments the counter for the given key.
func inc(num int) {
    lock.Lock()
    defer lock.Unlock()
    total += num
    wg.Done()
}

// Value returns the current value of the counter for the given key.
func getValue() int {
    lock.Lock()
    defer lock.Unlock()
    return total
}

Или используйте sync/atomic для доступа / изменения переменной.

person Burak Serdar    schedule 20.08.2020
comment
Привет, нужно ли использовать sync.lock внутри getValue? Поскольку он не находится внутри горутины и был вызван после функции wg.Wait () - person Eason; 20.08.2020
comment
Он находится в горутине, запущенной main(). Однако wg.Wait устанавливает связь «произошло до», поэтому, пока вызов getValue является единственным экземпляром этого вызова, он должен работать без блокировки. Интересно, видит ли детектор гонок это как гонку. - person Burak Serdar; 20.08.2020

Причина, по которой вы каждый раз получаете разные значения, - это состояние гонки в total += num.

Одно простое исправление - добавление мьютекса: var mu sync.Mutex

И используйте его в inc :

func inc(num int) {
    mu.Lock()
    defer mu.Unlock()
    total += num
    wg.Done()
}
person donmichael    schedule 20.08.2020

Уже упоминалось, что у вас есть гонка за данные, и использование Mutex - это решение. В качестве альтернативы вы можете использовать пакет atomic, который быстрее.

package main

import (
    "fmt"
    "sync"
    "sync/atomic"
)

var total uint64
var wg sync.WaitGroup

func inc(num uint64) {
    atomic.AddUint64(&total, 1)
    wg.Done()
}

// Value returns the current value of the counter for the given key.
func getValue() uint64 {
    return atomic.LoadUint64(&total)
}

func main() {
    for i := uint64(1); i <= 1000; i++ {
        wg.Add(1)
        go inc(i)
    }
    wg.Wait()
    fmt.Println(getValue())
}
person maxim_ge    schedule 20.08.2020
comment
Привет, AddUint64 по какой-то причине или AddUint32 тоже можно использовать? Какая производительность между атомарным и мьютексом. - person Eason; 20.08.2020
comment
@eason, используйте AddUint32(), если uint32 соответствует вашим потребностям. Производительность зависит от многих факторов. На моем стенде ПК вроде этого play.golang.org/p/Do9mTKX4ExV показывает 6 нс против 16 / op - person maxim_ge; 20.08.2020
comment
Это связано с моей 32-битной или 64-битной ОС? - person Eason; 21.08.2020
comment
@eason Использование uint64 на win32 нормально, и наоборот - person maxim_ge; 21.08.2020