Skip to content

Commit 6f2fbc6

Browse files
committed
sync: implement RWMutex using futexes
Somewhat surprisingly, this results in smaller code than the old code with the cooperative (tasks) scheduler. Probably because the new RWMutex is also simpler.
1 parent 5784e03 commit 6f2fbc6

File tree

1 file changed

+64
-99
lines changed

1 file changed

+64
-99
lines changed

src/sync/mutex.go

Lines changed: 64 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -7,131 +7,96 @@ import (
77

88
type Mutex = task.Mutex
99

10-
type RWMutex struct {
11-
// waitingWriters are all of the tasks waiting for write locks.
12-
waitingWriters task.Stack
13-
14-
// waitingReaders are all of the tasks waiting for a read lock.
15-
waitingReaders task.Stack
10+
//go:linkname runtimePanic runtime.runtimePanic
11+
func runtimePanic(msg string)
1612

17-
// state is the current state of the RWMutex.
18-
// Iff the mutex is completely unlocked, it contains rwMutexStateUnlocked (aka 0).
19-
// Iff the mutex is write-locked, it contains rwMutexStateWLocked.
20-
// While the mutex is read-locked, it contains the current number of readers.
21-
state uint32
13+
type RWMutex struct {
14+
// Reader count, with the number of readers that currently have read-locked
15+
// this mutex.
16+
// The value can be in two states: one where 0 means no readers and another
17+
// where -rwMutexMaxReaders means no readers. A base of 0 is normal
18+
// uncontended operation, a base of -rwMutexMaxReaders means a writer has
19+
// the lock or is trying to get the lock. In the second case, readers should
20+
// wait until the reader count becomes non-negative again to give the writer
21+
// a chance to obtain the lock.
22+
readers task.Futex
23+
24+
// Writer futex, normally 0. If there is a writer waiting until all readers
25+
// have unlocked, this value is 1. It will be changed to a 2 (and get a
26+
// wake) when the last reader unlocks.
27+
writer task.Futex
28+
29+
// Writer lock. Held between Lock() and Unlock().
30+
writerLock Mutex
2231
}
2332

24-
const (
25-
rwMutexStateUnlocked = uint32(0)
26-
rwMutexStateWLocked = ^uint32(0)
27-
rwMutexMaxReaders = rwMutexStateWLocked - 1
28-
)
33+
const rwMutexMaxReaders = 1 << 30
2934

3035
func (rw *RWMutex) Lock() {
31-
if rw.state == 0 {
32-
// The mutex is completely unlocked.
33-
// Lock without waiting.
34-
rw.state = rwMutexStateWLocked
36+
// Exclusive lock for writers.
37+
rw.writerLock.Lock()
38+
39+
// Flag that we need to be awakened after the last read-lock unlocks.
40+
rw.writer.Store(1)
41+
42+
// Signal to readers that they can't lock this mutex anymore.
43+
n := uint32(rwMutexMaxReaders)
44+
waiting := rw.readers.Add(-n)
45+
if int32(waiting) == -rwMutexMaxReaders {
46+
// All readers were already unlocked, so we don't need to wait for them.
47+
rw.writer.Store(0)
3548
return
3649
}
3750

38-
// Wait for the lock to be released.
39-
rw.waitingWriters.Push(task.Current())
40-
task.Pause()
51+
// There is at least one reader.
52+
// Wait until all readers are unlocked. The last reader to unlock will set
53+
// rw.writer to 2 and awaken us.
54+
for rw.writer.Load() == 1 {
55+
rw.writer.Wait(1)
56+
}
57+
rw.writer.Store(0)
4158
}
4259

4360
func (rw *RWMutex) Unlock() {
44-
switch rw.state {
45-
case rwMutexStateWLocked:
46-
// This is correct.
47-
48-
case rwMutexStateUnlocked:
49-
// The mutex is already unlocked.
50-
panic("sync: unlock of unlocked RWMutex")
51-
52-
default:
53-
// The mutex is read-locked instead of write-locked.
54-
panic("sync: write-unlock of read-locked RWMutex")
61+
// Signal that new readers can lock this mutex.
62+
waiting := rw.readers.Add(rwMutexMaxReaders)
63+
if waiting != 0 {
64+
// Awaken all waiting readers.
65+
rw.readers.WakeAll()
5566
}
5667

57-
switch {
58-
case rw.maybeUnblockReaders():
59-
// Switched over to read mode.
60-
61-
case rw.maybeUnblockWriter():
62-
// Transferred to another writer.
63-
64-
default:
65-
// Nothing is waiting for the lock.
66-
rw.state = rwMutexStateUnlocked
67-
}
68+
// Done with this lock (next writer can try to get a lock).
69+
rw.writerLock.Unlock()
6870
}
6971

7072
func (rw *RWMutex) RLock() {
71-
if rw.state == rwMutexStateWLocked {
72-
// Wait for the write lock to be released.
73-
rw.waitingReaders.Push(task.Current())
74-
task.Pause()
75-
return
76-
}
73+
// Add us as a reader.
74+
newVal := rw.readers.Add(1)
7775

78-
if rw.state == rwMutexMaxReaders {
79-
panic("sync: too many readers on RWMutex")
76+
// Wait until the RWMutex is available for readers.
77+
for int32(newVal) <= 0 {
78+
rw.readers.Wait(newVal)
79+
newVal = rw.readers.Load()
8080
}
81-
82-
// Increase the reader count.
83-
rw.state++
8481
}
8582

8683
func (rw *RWMutex) RUnlock() {
87-
switch rw.state {
88-
case rwMutexStateUnlocked:
89-
// The mutex is already unlocked.
90-
panic("sync: unlock of unlocked RWMutex")
91-
92-
case rwMutexStateWLocked:
93-
// The mutex is write-locked instead of read-locked.
94-
panic("sync: read-unlock of write-locked RWMutex")
95-
}
96-
97-
rw.state--
84+
// Remove us as a reader.
85+
one := uint32(1)
86+
readers := int32(rw.readers.Add(-one))
9887

99-
if rw.state == rwMutexStateUnlocked {
100-
// This was the last reader.
101-
// Try to unblock a writer.
102-
rw.maybeUnblockWriter()
88+
// Check whether RUnlock was called too often.
89+
if readers == -1 || readers == (-rwMutexMaxReaders)-1 {
90+
runtimePanic("sync: RUnlock of unlocked RWMutex")
10391
}
104-
}
10592

106-
func (rw *RWMutex) maybeUnblockReaders() bool {
107-
var n uint32
108-
for {
109-
t := rw.waitingReaders.Pop()
110-
if t == nil {
111-
break
93+
if readers == -rwMutexMaxReaders {
94+
// This was the last read lock. Check whether we need to wake up a write
95+
// lock.
96+
if rw.writer.CompareAndSwap(1, 2) {
97+
rw.writer.Wake()
11298
}
113-
114-
n++
115-
scheduleTask(t)
116-
}
117-
if n == 0 {
118-
return false
11999
}
120-
121-
rw.state = n
122-
return true
123-
}
124-
125-
func (rw *RWMutex) maybeUnblockWriter() bool {
126-
t := rw.waitingWriters.Pop()
127-
if t == nil {
128-
return false
129-
}
130-
131-
rw.state = rwMutexStateWLocked
132-
scheduleTask(t)
133-
134-
return true
135100
}
136101

137102
type Locker interface {

0 commit comments

Comments
 (0)