Golang sync map

·

3 min read

What is sync.Map

sync map is concurrent safe map data type.

Use Cases

used to store keys and values in multiple concurrent goroutines.

Example

valueStore is a sync map used to store values in multiple goroutines. after all goroutines finished store data in valueStore, Call Range to get all keys and values to result.

package main

import (
    "fmt"
    "sync"
)

func geValuesByKey(key string) []int {
    switch key {
    case "k1":
        return []int{1, 2, 3}
    case "k2":
        return []int{100, 200, 300}
    case "k6":
        return []int{8, 9, 10}
    }

    return []int{}
}

func concurrentGetValues(keys []string) map[string][]int {
    var valueStore sync.Map
    var wg sync.WaitGroup

    for _, key := range keys {
        wg.Add(1)
        go func(k string) {
            values := geValuesByKey(k)
            if len(values) > 0 {
                valueStore.Store(k, values)
            }
            wg.Done()
        }(key)
    }
    wg.Wait()

    result := make(map[string][]int, len(keys))
    valueStore.Range(func(key, value interface{}) bool {
        result[key.(string)] = value.([]int)
        return true
    })
    return result
}

func main() {
    keys := []string{"k1", "k2", "k6", "k3"}
    result := concurrentGetValues(keys)

    fmt.Printf("concurrentGetValues: %+v\n", result) // concurrentGetValues: map[k1:[1 2 3] k2:[100 200 300] k6:[8 9 10]]
}

The performance of sync map vs locked map

package bench

import "sync"

type LockedStore struct {
    sync.RWMutex
    m map[string]interface{}
}

func (s *LockedStore) Get(key string) interface{} {
    s.RLock()
    defer s.RUnlock()
    return s.m[key]
}

func (s *LockedStore) Set(key string, value interface{}) {
    s.Lock()
    s.m[key] = value
    s.Unlock()
}

Bench mark

package bench

import (
    "strconv"
    "sync"
    "testing"
)

var (
    number      = 1000000
    lockedStore = initStore(number, false)
    syncStore   = initSyncMap(number, false)
)

func initStore(n int, noValue bool) *LockedStore {
    s := &LockedStore{m: make(map[string]interface{}, n)}
    if noValue {
        return s
    }

    var i int
    for ; i < n; i++ {
        s.Set(strconv.Itoa(i), i)
    }
    return s
}

func initSyncMap(n int, noValue bool) sync.Map {
    var m sync.Map
    if noValue {
        return m
    }

    for i := 0; i < n; i++ {
        m.Store(strconv.Itoa(i), i)
    }
    return m
}

func BenchmarkSyncMapGet(b *testing.B) {
    n := b.N
    var wg sync.WaitGroup
    wg.Add(n)
    for i := 0; i < n; i++ {
        k := strconv.Itoa(i)
        go func(key string) {
            syncStore.Load(key)
            wg.Done()
        }(k)
    }
    wg.Wait()
}

func BenchmarkSyncMapSet(b *testing.B) {
    n := b.N
    syncStore = initSyncMap(n, true)

    var wg sync.WaitGroup
    wg.Add(n)
    for i := 0; i < n; i++ {
        k := strconv.Itoa(i)
        go func(key string) {
            syncStore.Store(key, ":"+key+":")
            wg.Done()
        }(k)
    }
    wg.Wait()
}

func BenchmarkLockedStore_Get(b *testing.B) {
    n := b.N
    var wg sync.WaitGroup
    wg.Add(n)
    for i := 0; i < n; i++ {
        k := strconv.Itoa(i)
        go func(key string) {
            lockedStore.Get(key)
            wg.Done()
        }(k)
    }
    wg.Wait()
}

func BenchmarkLockedStore_Set(b *testing.B) {
    n := b.N
    lockedStore = initStore(n, true)
    var wg sync.WaitGroup
    wg.Add(n)
    for i := 0; i < n; i++ {
        k := strconv.Itoa(i)
        go func(key string) {
            lockedStore.Set(key, ":"+key+":")
            wg.Done()
        }(k)
    }
    wg.Wait()
}
/*Bench Result
go test -bench=.
goos: darwin
goarch: amd64
pkg: go-common/bench
BenchmarkSyncMapGet-8            1000000              1051 ns/op
BenchmarkSyncMapSet-8             805191              2834 ns/op
BenchmarkLockedStore_Get-8       2943969               390 ns/op
BenchmarkLockedStore_Set-8       1281457               989 ns/op
*/