GolangWebDev
GolangWebDev
1670 0 0

Use map with atomic/sync package

if you only have one writer, then you can probably get away with using an atomic Value.

Example Value ReadMostly

The following example shows how to maintain a scalable frequently read, but infrequently updated data structure using copy-on-write idiom.

import (
	"sync"
	"sync/atomic"
)

func main() {
	type Map map[string]string
	var m atomic.Value
	m.Store(make(Map))
	var mu sync.Mutex // used only by writers
	// read function can be used to read the data without further synchronization
	read := func(key string) (val string) {
		m1 := m.Load().(Map)
		return m1[key]
	}
	// insert function can be used to update the data without further synchronization
	insert := func(key, val string) {
		mu.Lock() // synchronize with other potential writers
		defer mu.Unlock()
		m1 := m.Load().(Map) // load current value of the data structure
		m2 := make(Map)      // create a new value
		for k, v := range m1 {
			m2[k] = v // copy all data from the current object to the new one
		}
		m2[key] = val // do the update that we need
		m.Store(m2)   // atomically replace the current object with the new one
		// At this point all new readers start working with the new version.
		// The old version will be garbage collected once the existing readers
		// (if any) are done with it.
	}
	_, _ = read, insert
}

More https://pkg.go.dev/sync/atomic#example-Value-ReadMostly

The following example shows how to use Value for periodic program config updates and propagation of the changes to worker goroutines.

func loadConfig() map[string]string {
	return make(map[string]string)
}

func requests() chan int {
	return make(chan int)
}

func main() {
	var config atomic.Value // holds current server configuration
	// Create initial config value and store into config.
	config.Store(loadConfig())
	go func() {
		// Reload config every 10 seconds
		// and update config value with the new version.
		for {
			time.Sleep(10 * time.Second)
			config.Store(loadConfig())
		}
	}()
	// Create worker goroutines that handle incoming requests
	// using the latest config value.
	for i := 0; i < 10; i++ {
		go func() {
			for r := range requests() {
				c := config.Load()
				// Handle request r using config c.
				_, _ = r, c
			}
		}()
	}
}

Other example, supports multiple writers/readers.

package main

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

// This function allows us to create pointers from literals
func intPtr(newInt int64) *int64 {
	return &newInt
}

func main() {
	var wg sync.WaitGroup

	mapX := map[int]*int64{0: intPtr(1), 1: intPtr(2)}
	go func() {
		for {
			// atomic version of mapX[0]++
			atomic.AddInt64(mapX[0], 1)
		}
	}()

	go func() {
		for {
			// atomic version of mapX[1]++
			atomic.AddInt64(mapX[1], 1)
		}
	}()

	wg.Add(5)
	for i := 0; i < 5; i++ {
		go func() {
			defer wg.Done()

			for key, value := range mapX {
				// we need to use atomic.LoadInt64 to synchronize with the atomic.AddInt64 calls
				fmt.Printf("%d = %d\n", key, atomic.LoadInt64(value))
			}
		}()
	}

	wg.Wait()
}

Output

0 = 1
1 = 2
0 = 2727
1 = 14512
0 = 21160
1 = 23904
0 = 48994
1 = 92327
0 = 50166
1 = 95534

Used unsafe & atomic

package main

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

var unsafeData = map[string]string{}

func data() map[string]string {
	p := atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&unsafeData)))
	return *((*map[string]string)(unsafe.Pointer(&p)))
}

func setData(m map[string]string) {
	atomic.StorePointer(
		(*unsafe.Pointer)(unsafe.Pointer(&unsafeData)),
		*((*unsafe.Pointer)(unsafe.Pointer(&m))))
}

func main() {
	wg := sync.WaitGroup{}
	wg.Add(2)

	go func() {
		temp := map[string]string{"a": "a"}
		setData(temp)
		wg.Done()
	}()

	go func() {
		v := data()["d"]
		fmt.Println(v)
		wg.Done()
	}()

	wg.Wait()

	fmt.Println(data())
}
0

See Also


Discussion

Login Topics