Skip to content

Commit

Permalink
sync: add Mutex.TryLock, RWMutex.TryLock, RWMutex.TryRLock
Browse files Browse the repository at this point in the history
Use of these functions is almost (but not) always a bad idea.

Very rarely they are necessary, and third-party implementations
(using a mutex and an atomic word, say) cannot integrate as well
with the race detector as implmentations in package sync itself.

Fixes #45435.

Change-Id: I0128ca48ef5e0a3b09c913f0f3a7ee5c56388000
Reviewed-on: https://go-review.googlesource.com/c/go/+/319769
Trust: Russ Cox <rsc@golang.org>
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
  • Loading branch information
rsc committed Oct 29, 2021
1 parent 3aecb3a commit 645d078
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 0 deletions.
15 changes: 15 additions & 0 deletions src/sync/mutex.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,21 @@ func (m *Mutex) Lock() {
m.lockSlow()
}

// TryLock tries to lock m and reports whether it succeeded.
//
// Note that while correct uses of TryLock do exist, they are rare,
// and use of TryLock is often a sign of a deeper problem
// in a particular use of mutexes.
func (m *Mutex) TryLock() bool {
if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
if race.Enabled {
race.Acquire(unsafe.Pointer(m))
}
return true
}
return false
}

func (m *Mutex) lockSlow() {
var waitStartTime int64
starving := false
Expand Down
18 changes: 18 additions & 0 deletions src/sync/mutex_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ func BenchmarkContendedSemaphore(b *testing.B) {

func HammerMutex(m *Mutex, loops int, cdone chan bool) {
for i := 0; i < loops; i++ {
if i%3 == 0 {
if m.TryLock() {
m.Unlock()
}
continue
}
m.Lock()
m.Unlock()
}
Expand All @@ -71,7 +77,19 @@ func TestMutex(t *testing.T) {
t.Logf("got mutexrate %d expected 0", n)
}
defer runtime.SetMutexProfileFraction(0)

m := new(Mutex)

m.Lock()
if m.TryLock() {
t.Fatalf("TryLock succeeded with mutex locked")
}
m.Unlock()
if !m.TryLock() {
t.Fatalf("TryLock failed with mutex unlocked")
}
m.Unlock()

c := make(chan bool)
for i := 0; i < 10; i++ {
go HammerMutex(m, 1000, c)
Expand Down
59 changes: 59 additions & 0 deletions src/sync/rwmutex.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,34 @@ func (rw *RWMutex) RLock() {
}
}

// TryRLock tries to lock rw for reading and reports whether it succeeded.
//
// Note that while correct uses of TryRLock do exist, they are rare,
// and use of TryRLock is often a sign of a deeper problem
// in a particular use of mutexes.
func (rw *RWMutex) TryRLock() bool {
if race.Enabled {
_ = rw.w.state
race.Disable()
}
for {
c := atomic.LoadInt32(&rw.readerCount)
if c < 0 {
if race.Enabled {
race.Enable()
}
return false
}
if atomic.CompareAndSwapInt32(&rw.readerCount, c, c+1) {
if race.Enabled {
race.Enable()
race.Acquire(unsafe.Pointer(&rw.readerSem))
}
return true
}
}
}

// RUnlock undoes a single RLock call;
// it does not affect other simultaneous readers.
// It is a run-time error if rw is not locked for reading
Expand Down Expand Up @@ -122,6 +150,37 @@ func (rw *RWMutex) Lock() {
}
}

// TryLock tries to lock rw for writing and reports whether it succeeded.
//
// Note that while correct uses of TryLock do exist, they are rare,
// and use of TryLock is often a sign of a deeper problem
// in a particular use of mutexes.
func (rw *RWMutex) TryLock() bool {
if race.Enabled {
_ = rw.w.state
race.Disable()
}
if !rw.w.TryLock() {
if race.Enabled {
race.Enable()
}
return false
}
if !atomic.CompareAndSwapInt32(&rw.readerCount, 0, -rwmutexMaxReaders) {
rw.w.Unlock()
if race.Enabled {
race.Enable()
}
return false
}
if race.Enabled {
race.Enable()
race.Acquire(unsafe.Pointer(&rw.readerSem))
race.Acquire(unsafe.Pointer(&rw.writerSem))
}
return true
}

// Unlock unlocks rw for writing. It is a run-time error if rw is
// not locked for writing on entry to Unlock.
//
Expand Down
28 changes: 28 additions & 0 deletions src/sync/rwmutex_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,34 @@ func HammerRWMutex(gomaxprocs, numReaders, num_iterations int) {
}

func TestRWMutex(t *testing.T) {
var m RWMutex

m.Lock()
if m.TryLock() {
t.Fatalf("TryLock succeeded with mutex locked")
}
if m.TryRLock() {
t.Fatalf("TryRLock succeeded with mutex locked")
}
m.Unlock()

if !m.TryLock() {
t.Fatalf("TryLock failed with mutex unlocked")
}
m.Unlock()

if !m.TryRLock() {
t.Fatalf("TryRLock failed with mutex unlocked")
}
if !m.TryRLock() {
t.Fatalf("TryRLock failed with mutex rlocked")
}
if m.TryLock() {
t.Fatalf("TryLock succeeded with mutex rlocked")
}
m.RUnlock()
m.RUnlock()

defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(-1))
n := 1000
if testing.Short() {
Expand Down

0 comments on commit 645d078

Please sign in to comment.