RWMutex-and-sync.Map
In the last post, I noted a problem of read-and-write in high-cocurrency situation and finally chose to use sync.Map
. This post I will make a comparation between map with RWMutex
and sync.Map
.
Read-or-Write Test
Code
package main
import (
"fmt"
"sync"
"time"
)
type rwMap struct {
data map[int]int
m sync.RWMutex
}
var (
rwm = rwMap{data: make(map[int]int)}
end = int(1e7)
syncM sync.Map
)
func main() {
fmt.Println("write test")
// rw map
start := time.Now()
for i := 0; i < end; i++ {
rwm.m.Lock()
rwm.data[i] = i
rwm.m.Unlock()
}
fmt.Printf("rw map used %v\n", time.Since(start))
// sync Map
start = time.Now()
for i := 0; i < end; i++ {
syncM.Store(i, i)
}
fmt.Printf("sync map used %v\n\n", time.Since(start))
fmt.Println("read test")
// rw map
start = time.Now()
for i := 0; i < end; i++ {
rwm.m.RLock()
_ = rwm.data[i]
rwm.m.RUnlock()
}
fmt.Printf("rw map used %v\n", time.Since(start))
// sync Map
start = time.Now()
for i := 0; i < end; i++ {
_, _ = syncM.Load(i)
}
fmt.Printf("sync map used %v\n\n", time.Since(start))
}
Result
➜ comparation go run main.go
write test
rw map used 3.012520671s
sync map used 10.648790268s
read test
rw map used 1.124830897s
sync map used 2.328729454s
➜ comparation
We can see that sync.Map
is not as good as we except in this situation.
Cocurrency Read-Write Test
Let’s do some test in high-cocurrency.
map with rw-mutex
package main
import (
"fmt"
"sync"
"sync/atomic"
"time"
)
type rwMap struct {
data map[int]int
m sync.RWMutex
}
var (
rwm = rwMap{data: make(map[int]int)}
end = int(1e7)
// syncM sync.Map
readTimes uint64
)
func init() {
for i := 0; i < end; i++ {
rwm.data[i] = i
}
}
func read() {
for {
for i := 0; i < end; i++ {
rwm.m.RLock()
atomic.AddUint64(&readTimes, 1)
if rwm.data[i] != i {
panic("!")
}
rwm.m.RUnlock()
}
}
}
func write() {
n := time.Now().Nanosecond() % 1000
rwm.m.Lock()
rwm.data[n] = n
rwm.m.Unlock()
}
func main() {
start := time.Now()
// 10 gouroutine to read data
for i := 0; i < 10; i++ {
go read()
}
time.Sleep(time.Second)
// write 100 times
for i := 0; i < 100; i++ {
write()
}
fmt.Printf("read %d, used %v\n", readTimes, time.Since(start.Add(-time.Second)))
}
I got:
➜ comparation go run main.go
read 13163604, used 2.019429333s
➜ comparation
sync.Map
package main
import (
"fmt"
"sync"
"sync/atomic"
"time"
)
type rwMap struct {
data map[int]int
m sync.RWMutex
}
var (
rwm = rwMap{data: make(map[int]int)}
end = int(1e7)
syncM sync.Map
readTimes uint64
)
func init() {
for i := 0; i < end; i++ {
rwm.data[i] = i
}
}
func read() {
for {
for i := 0; i < end; i++ {
atomic.AddUint64(&readTimes, 1)
syncM.Load(i)
}
}
}
func write() {
n := time.Now().UnixNano() % 1000
syncM.Store(n, n)
}
func main() {
start := time.Now()
// 10 gouroutine to read data
for i := 0; i < 10; i++ {
go read()
}
time.Sleep(time.Second)
// write 100 times
for i := 0; i < 100; i++ {
write()
}
fmt.Printf("read %d, used %v\n", readTimes, time.Since(start.Add(-time.Second)))
}
And the result:
➜ comparation go run main.go
read 95246260, used 2.30790947s
➜ comparation
I did got the difference: sync.Map
cocurrent read ability is much better than a map with rw mutex.
Conclusion
sync.Map
is suitable for the scenes with high-cocurrency read and fewer write actions.
But in the cases whitch a map is read or write in single goroutine, you’d better use a map with rw-mutex.