Concurrent Go Map
This repository serves as a set of examples for making maps that are accessible in concurrent Go software. The types can be used as a library, each with their own performance characteristics, but I wrote it to determine which method produced the most readable code, and the most performant code.
This library exposes the Congomap
interface, and three concrete
types that adhere to that interface. All three concrete types are
available here because they have individual performance
characteristics, where one concrete type may be more appropriate for a
desired used that one of the other types.
Creating a Congomap
is done by calling the instantiation function of
the desired concrete type.
NOTE: To prevent resource leakage, always call the Halt
method on a
Congomap
after it is no longer needed.
A sync mutex map uses simple read/write mutex primitives from the
sync
package. This results in a highly performant way of
synchronizing reads and writes to the map. This is about seven times
the performance of the NewChannelMap
method and is the fastest of
those tested in this library.
cgm, _ := cmap.NewSyncMutexMap()
defer cgm.Halt()
A sync atomic map uses the algorithm suggested in the documentation
for sync/atomic
. It is designed for when a map is read many, many
more times than it is written. Performance also depends on the number
of the keys in the map.
cgm,_ := congomap.NewSyncAtomicMap()
defer cgm.Halt()
A channel map is modeled after the Go way of sharing memory: by communicating over channels. Reads and writes are serialized by a Go routine processing anonymous functions. Not as fast as the other methods.
cgm, _ := cmap.NewChannelMap()
defer cgm.Halt()
Storing key value pairs in a Congomap
is done with the Store method.
cgm.Store("someKey", 42)
cgm.Store("someOtherKey", struct{}{})
cgm.Store("yetAnotherKey", make(chan interface{}))
Loading values already stored in a Congomap
is done with the Load
method.
value, ok := cgm.Load("someKey")
if !ok {
// key is not in the Congomap
}
Some maps are used as a lazy lookup device. When a key is not already
found in the map, the callback function is invoked with the specified
key. If the callback function returns an error, then a nil value and
the error is returned from LoadStore
. If the callback function
returns no error, then the returned value is stored in the Congomap
and returned from LoadStore
.
// Define a lookup function to be invoked when LoadStore is called
// for a key not stored in the Congomap.
lookup := func(key string) (interface{}, error) {
return someLenghyComputation(key), nil
}
// Create a Congomap, specifying what the lookup callback function
// is.
cgm, err := congomap.NewSyncMutexMap(congomap.Lookup(lookup))
if err != nil {
log.Fatal(err)
}
// You can still use the regular Load and Store functions, which will
// not involve the lookup function.
cgm.Store("someKey", 42)
value, ok := cgm.Load("someKey")
if !ok {
// key is not in the Congomap
}
// When you use the LoadStore function, and the key is not in the
// Congomap, the lookup funciton is invoked, and the return value is
// stored in the Congomap and returned to the program.
value, err := cgm.LoadStore("someKey")
if err != nil {
// lookup functio returned an error
}
Part of the purpose of this library is to calculate the relative performance of these approaches to access to a concurrent map. Here's a sample run on my Mac using Go 1.4:
go test -bench .
PASS
BenchmarkChannelMapLoad 1000000 1533 ns/op
BenchmarkChannelMapLoadStore 1000000 1569 ns/op
BenchmarkSyncAtomicMapLoad 5000000 262 ns/op
BenchmarkSyncAtomicMapLoadTTL 3000000 412 ns/op
BenchmarkSyncAtomicMapLoadStore 10000000 229 ns/op
BenchmarkSyncMutexMapLoad 10000000 197 ns/op
BenchmarkSyncMutexMapLoadTTL 10000000 197 ns/op
BenchmarkSyncMutexMapLoadStore 10000000 213 ns/op
ok github.com/karrick/congomap 15.676s