Насколько безопасны карты Голанга для одновременных операций чтения/записи?

Согласно блогу Go,

Карты небезопасны для одновременного использования: он не определяет, что происходит, когда вы читаете и записываете их одновременно. Если вам нужно читать и записывать на карту из одновременного выполнения goroutines, доступ должен быть опосредован каким-то механизмом синхронизации. (источник: https://blog.golang.org/go-maps-in-action)

Может ли кто-нибудь уточнить это? Параллельные операции чтения кажутся допустимыми во всех подпрограммах, но одновременные операции чтения/записи могут генерировать условие гонки, если вы пытаетесь прочитать и записать на тот же ключ.

Может ли этот последний риск быть уменьшен в некоторых случаях? Например:

  • Функция A порождает k и устанавливает m [k] = 0. Это единственный раз, когда A пишет для отображения m. k, как известно, не находится в m.
  • A передает k в функцию B, выполняющуюся одновременно
  • A затем читает m [k]. Если m [k] == 0, он ждет, продолжая только тогда, когда m [k]!= 0
  • B ищет k на карте. Если он найдет это, B устанавливает m [k] на некоторое положительное целое число. Если он не ждет, пока k не будет в m.

Это не код (очевидно), но я думаю, что он показывает контуры случая, когда даже если A и B пытаются получить доступ к m, не будет состояния гонки, или если это не будет иметь значения из-за дополнительных ограничений.

Ответ 1

До Golang 1.6 одновременное чтение в порядке, одновременная запись не в порядке, но запись и одновременное чтение в порядке. Начиная с Golang 1.6, карта не может быть прочитана при ее написании. Итак, после Golang 1.6, параллельная карта доступа должна быть такой:

package main

import (
    "sync"
    "time"
)

var m = map[string]int{"a": 1}
var lock = sync.RWMutex{}

func main() {
    go Read()
    time.Sleep(1 * time.Second)
    go Write()
    time.Sleep(1 * time.Minute)
}

func Read() {
    for {
        read()
    }
}

func Write() {
    for {
        write()
    }
}

func read() {
    lock.RLock()
    defer lock.RUnlock()
    _ = m["a"]
}

func write() {
    lock.Lock()
    defer lock.Unlock()
    m["b"] = 2
}

Или вы получите сообщение об ошибке ниже: введите описание изображения здесь

ДОБАВЛЕНО:

Вы можете обнаружить гонку, используя go run -race race.go

Измените функцию read:

func read() {
    // lock.RLock()
    // defer lock.RUnlock()
    _ = m["a"]
}

введите описание изображения здесь

Другой выбор:

Как мы знаем, map был реализован ведрами, а sync.RWMutex заблокирует все ведра. concurrent-map использовать fnv32, чтобы очертить ключ, и каждый ковш использует один sync.RWMutex.

Ответ 2

Параллельное чтение (только чтение) в порядке. Параллельная запись и/или чтение не в порядке.

Несколько goroutines могут записывать и/или читать одну и ту же карту, если синхронизирован доступ, например. через пакет sync с каналами или другими способами.

Ваш пример:

  • Функция A порождает k и устанавливает m [k] = 0. Это единственный раз, когда A пишет для отображения m. k, как известно, не находится в m.
  • A передает k в функцию B, выполняющуюся одновременно
  • A затем читает m [k]. Если m [k] == 0, он ждет, продолжая только тогда, когда m [k]!= 0
  • B ищет k на карте. Если он найдет это, B устанавливает m [k] на некоторое положительное целое число. Если он не ждет, пока k не будет в m.

В вашем примере есть 2 goroutines: A и B, а A пытается прочитать m (на шаге 3), а B пытается записать его (на шаге 4) одновременно. Синхронизация отсутствует (вы ничего не упомянули), поэтому это одно не разрешено/не определено.

Что это значит? Не определено, даже если B пишет m, A никогда не может наблюдать изменение. Или A может наблюдать изменения, которые даже не произошли. Или может возникнуть паника. Или Земля может взорваться из-за этого несинхронизированного параллельного доступа (хотя вероятность этого последнего случая чрезвычайно мала, может быть, даже меньше 1е-40).

Похожие вопросы:

Карта с одновременным доступом

что не является потокобезопасным средством отображения карт в Google?

В чем опасность пренебрежения goroutine/безопасностью потоков при использовании карты в Go?

Ответ 3

Перейти к версии 1.6

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

Карты представляют собой сложные, самоорганизующиеся структуры данных. Параллельный доступ для чтения и записи составляет undefined.

Без кода, еще нечего сказать.

Ответ 4

Как сказано в других ответах, родной тип map не является goroutine -safe. Несколько примечаний после прочтения текущих ответов:

  • Не используйте defer для разблокировки, у него есть некоторые накладные расходы, которые влияют на производительность (см. этот хороший пост). Вызовите разблокировку напрямую.
  • Вы можете добиться более высокой производительности за счет сокращения времени, затрачиваемого на блокировки. Например, путем наложения карты.
  • Существует общий пакет (приближающийся к 400 звездам на GitHub), который используется для решения этой проблемы concurrent-map здесь, которая имеет производительность и удобство использования в разум. Вы можете использовать его для обработки проблем concurrency для вас.

Ответ 5

Вы можете сохранить указатель на int на карте и иметь несколько goroutines, на которые указывает int, а другая записывает новое значение в int. В этом случае карта не обновляется.

Это не было бы идиоматичным для Go, а не тем, что вы просили.

Или вместо того, чтобы передавать ключ к карте, вы можете передать индекс в массив и обновить его одним goroutine, в то время как другие будут читать местоположение.

Но вам, вероятно, просто интересно, почему значение карты не может быть обновлено с новым значением, когда ключ уже находится на карте. По-видимому, ничего о схеме хэширования карты не меняется - по крайней мере, не дается их текущая реализация. Казалось бы, авторы Go не хотят получать надбавки за такие особые случаи. Как правило, они хотят, чтобы код был легко читаемым и понятным, и правило, подобное тому, которое не позволяет записывать карты, когда другие горуты могут читать, делает вещи простыми, и теперь в 1.6 они могут даже начать улавливать неправильное использование во время обычного времени автономной работы - это позволяет многим людям много часов отладки.