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.

comments powered by Disqus