Подтвердить что ты не робот

Как заблокировать/синхронизировать доступ к переменной в Go во время параллельных goroutines?

В своем ответе на этот вопрос: Golang для неустойчивого поведения Windows? user @distributed рекомендуется блокировать/синхронизировать доступ к общей переменной в параллельных goroutines.

Как я могу это сделать?

Подробнее о проблеме:

Я получаю этот код (возвращаемая функция с закрытием на views), работающий одновременно на нескольких goroutines:

func makeHomeHandler() func(c *http.Conn, r *http.Request) {
    views := 1
    return func(c *http.Conn, r *http.Request) {
        fmt.Fprintf(c, "Counting %s, %d so far.", r.URL.Path[1:], views)
        views++
    }
}

Похоже, что функция ввода-вывода занимает это время, и в результате я получаю такой вывод:

Counting monkeys, 5 so far.
Counting monkeys, 5 so far.
Counting monkeys, 5 so far.
Counting monkeys, 8 so far.
Counting monkeys, 8 so far.
Counting monkeys, 8 so far.
Counting monkeys, 11 so far.

Он увеличивается, но когда он печатается, я вижу, что операция print + incrementing не является атомарной вообще.

Если я изменил его на:

func makeHomeHandler() func(c *http.Conn, r *http.Request) {
    views := 0
    return func(c *http.Conn, r *http.Request) {
        views++
        // I can only hope that other goroutine does not increment the counter 
        // at this point, i.e., right after the previous line and before the 
        // next one are executed!
        views_now := views
        fmt.Fprintf(c, "Counting %s, %d so far.", r.URL.Path[1:], views_now)
    }
}

Кажется, все нормально, но я не совсем уверен, не закончится ли это в конце концов...

4b9b3361

Ответ 1

Если вам нужен синхронизированный счетчик, то использование sync.Mutex - это каноническое решение. Пакет sync/atomic следует использовать только для материалов низкого уровня или когда вы измеряете серьезную проблему с производительностью.

type Counter struct {
    mu  sync.Mutex
    x   int64
}

func (c *Counter) Add(x int64) {
    c.mu.Lock()
    c.x += x
    c.mu.Unlock()
}

func (c *Counter) Value() (x int64) {
    c.mu.Lock()
    x = c.x
    c.mu.Unlock()
    return
}

func makeHomeHandler() func(c http.ResponseWriter, r *http.Request) {
    var views Counter
    return func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Counting %s, %d so far.", r.URL.Path[1:], views.Value())
        views.Add(1)
    }
}

Для вашей конкретной проблемы я бы предложил определить новый тип, который удовлетворяет интерфейсу http.Handler, а не возвращать закрытие. Это выглядит проще:

type homeHandler struct {
    mu  sync.Mutex
    views   int64
}

func (h *homeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    h.mu.Lock()
    defer h.mu.Unlock()
    fmt.Fprintf(w, "Counting %s, %d so far.", r.URL.Path[1:], h.views)
    h.views++
}

func init() {
    http.Handle("/", new(homeHandler))
}

Ответ 2

В пакете sync есть некоторые примитивы синхронизации. В зависимости от проблемы вы можете использовать RWMutex или простой Mutex.

Если вы хотите получить более конкретный ответ, пожалуйста, дайте дополнительную информацию о том, для чего он нужен.

Изменить: после прочтения связанного вопроса вы, вероятно, ищете sync/atomic, хотя Mutex тоже прекрасен.

Edit2: Я видел, как вы обновили сообщение на примере. Здесь код с использованием sync/atomic.

func makeHomeHandler() func(w http.ResponseWriter, r *http.Request) {
    var views *uint64 = new(uint64)
    atomic.StoreUint64(views, 0) // I don't think this is strictly necessary
    return func(w http.ResponseWriter, r *http.Request) {
        // Atomically add one to views and get the new value
        // Perhaps you want to subtract one here
        views_now := atomic.AddUint64(views, 1) 
        fmt.Fprintf(w, "Counting %s, %d so far.", r.URL.Path[1:], views_now)
    }
}

(Примечание: я не тестировал выше, чтобы могли быть опечатки/мозги) Я протестировал его сейчас.