Skip to content

Commit

Permalink
split: add scalable RWMutex example and benchmark
Browse files Browse the repository at this point in the history
  • Loading branch information
aclements committed Mar 6, 2018
1 parent 3f09e4f commit 2a683d5
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 0 deletions.
10 changes: 10 additions & 0 deletions split/bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,13 @@ func BenchmarkCounterSequential(b *testing.B) {
}
})
}

func BenchmarkRWMutex(b *testing.B) {
var m RWMutex

b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
m.RLock().RUnlock()
}
})
}
104 changes: 104 additions & 0 deletions split/example_rwmutex_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package split

import (
"sync"
"sync/atomic"
)

// RWMutex is a scalable reader/writer mutual exclusion lock. The lock
// can be held by an arbitrary number of readers or a single writer.
// The zero value for a RWMutex is an unlocked mutex.
//
// In contrast with sync.RWMutex, this lock attempts to scale to any
// number of cores simultaneously acquiring read locks. However, this
// makes obtaining the lock in write mode more expensive.
type RWMutex struct {
readLocks *Value
writeLock sync.Mutex
initLock sync.Mutex
init uint32
}

// doInit performs lazily initialization on the first use of m.
func (m *RWMutex) doInit() {
// Acquire the initialization lock to protect against
// concurrent initialization.
m.initLock.Lock()
defer m.initLock.Unlock()
if atomic.LoadUint32(&m.init) != 0 {
// Another goroutine initialized the mutex while we
// were waiting on the shard lock.
return
}
m.readLocks = New(func(*sync.Mutex) {
// Block creating new shards while the write lock is
// held.
m.writeLock.Lock()
m.writeLock.Unlock()
})
atomic.StoreUint32(&m.init, 1)
}

// Lock acquires m in writer mode. This blocks all readers and
// writers.
func (m *RWMutex) Lock() {
if atomic.LoadUint32(&m.init) == 0 {
m.doInit()
}
// Block other writers and creation of new shards.
m.writeLock.Lock()
// Acquire all read locks.
m.readLocks.Range(func(s *sync.Mutex) {
s.Lock()
})
}

// Unlock releases m from writer mode. The mutex must currently be
// held in writer mode.
func (m *RWMutex) Unlock() {
m.readLocks.Range(func(s *sync.Mutex) {
s.Unlock()
})
m.writeLock.Unlock()
}

// RWMutexRUnlocker is a token used to unlock an RWMutex in read mode.
type RWMutexRUnlocker struct {
shard *sync.Mutex
}

// RLock acquires m in read mode. This blocks other goroutines from
// acquiring it in write mode, but does not generally block them from
// acquiring it in read mode. The caller must used the returned
// RWMutexRUnlocker to release the lock.
func (m *RWMutex) RLock() RWMutexRUnlocker {
if atomic.LoadUint32(&m.init) == 0 {
m.doInit()
}
shard := m.readLocks.Get().(*sync.Mutex)
shard.Lock()
return RWMutexRUnlocker{shard}
}

// RUnlock releases an RWMutex from read mode.
func (c RWMutexRUnlocker) RUnlock() {
c.shard.Unlock()
}

func Example_rwMutex() {
var m RWMutex

var wg sync.WaitGroup
for i := 0; i < 64; i++ {
wg.Add(1)
go func() {
m.RLock().RUnlock()
wg.Done()
}()
}
wg.Wait()
}

0 comments on commit 2a683d5

Please sign in to comment.