Я использую метод sync.MapLoadOrStore в Go. Я пытаюсь понять, может ли возникнуть состояние гонки, приводящее к множественным вычислениям функции создания ценности. Я пытаюсь понять, верно ли это и как с этим правильно справиться.
package main
import (
"fmt"
"sync"
)
type HashIDMap struct {
m sync.Map
}
func (hm *HashIDMap) GetOrCreate(key, value string, createFunc func(string, string)) string {
actual, loaded := hm.m.LoadOrStore(key, value)
if !loaded {
// If the value was not loaded, it means we stored it and need to create the database entry
createFunc(key, value)
}
return actual.(string)
}
func createDatabaseEntry(key, value string) {
// Simulate database entry creation
fmt.Printf("Creating database entry for key: %s with value: %s\n", key, value)
}
func main() {
hm := &HashIDMap{}
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
id := hm.GetOrCreate("hash_1", "id_1", createDatabaseEntry)
fmt.Println("Goroutine 1 got ID:", id)
}()
go func() {
defer wg.Done()
id := hm.GetOrCreate("hash_1", "id_1", createDatabaseEntry)
fmt.Println("Goroutine 2 got ID:", id)
}()
wg.Wait()
}
Я читал предположения, что createFunc действительно может быть выполнен более одного раза из-за состояния гонки.
Насколько я понимаю, это именно то, что LoadOrStore пытается решить, но теперь я больше не уверен, действительно ли возможно состояние гонки.
Возможно ли, чтобы createDatabaseEntry вызывался дважды для одного и того же ключа?

Это не приводит к состоянию гонки. Предложение, которое вы прочитали, неверно. В документации sync.Map говорится (выделено мной):
В терминологии модели памяти Go Map организует так, что операция записи «синхронизируется перед» любой операцией чтения, которая наблюдает эффект записи, где операции чтения и записи определяются следующим образом. Load, LoadAndDelete, LoadOrStore, Swap, CompareAndSwap и CompareAndDelete — операции чтения; Удаление, LoadAndDelete, Store и Swap — это операции записи; LoadOrStore — это операция записи, когда она возвращает загруженное значение false.
Поэтому, как и следовало ожидать, вызов LoadOrStore, который возвращает false (операция записи), гарантированно произойдет до вызова LoadOrStore, который возвращает true (операция чтения) для того же ключа.
Это означает, что только одна из горутин, одновременно вызывающих LoadOrStore с одним и тем же ключом, увидит loaded=false и вызовет createDatabaseEntry.