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
- Pipe output from one shell command to another using golang
- Do net.Conn unit test using net.Pipe in Golang
- Generate and composite thumbnails from images using Go
- Convert Civ 5 map to an image
- Golang mod import private package