Skip to content

Commit ff8615e

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 8b39f07 commit ff8615e

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
@@ -12,6 +12,9 @@ type Mutex struct {
1212
//go:linkname scheduleTask runtime.scheduleTask
1313
func scheduleTask(*task.Task)
1414

15+
//go:linkname runtimePanic runtime.runtimePanic
16+
func runtimePanic(msg string)
17+
1518
func (m *Mutex) Lock() {
1619
// Fast path: try to take an uncontended lock.
1720
if m.futex.CompareAndSwap(0, 1) {
@@ -52,130 +55,92 @@ func (m *Mutex) TryLock() bool {
5255
}
5356

5457
type RWMutex struct {
55-
// waitingWriters are all of the tasks waiting for write locks.
56-
waitingWriters task.Stack
57-
58-
// waitingReaders are all of the tasks waiting for a read lock.
59-
waitingReaders task.Stack
60-
61-
// state is the current state of the RWMutex.
62-
// Iff the mutex is completely unlocked, it contains rwMutexStateUnlocked (aka 0).
63-
// Iff the mutex is write-locked, it contains rwMutexStateWLocked.
64-
// While the mutex is read-locked, it contains the current number of readers.
65-
state uint32
58+
// Reader count, with the number of readers that currently have read-locked
59+
// this mutex.
60+
// The value can be in two states: one where 0 means no readers and another
61+
// where -rwMutexMaxReaders means no readers. A base of 0 is normal
62+
// uncontended operation, a base of -rwMutexMaxReaders means a writer has
63+
// the lock or is trying to get the lock. In the second case, readers should
64+
// wait until the reader count becomes non-negative again to give the writer
65+
// a chance to obtain the lock.
66+
readers task.Futex
67+
68+
// Writer futex, normally 0. If there is a writer waiting until all readers
69+
// have unlocked, this value is 1. It will be changed to a 2 (and get a
70+
// wake) when the last reader unlocks.
71+
writer task.Futex
72+
73+
// Writer lock. Held between Lock() and Unlock().
74+
writerLock Mutex
6675
}
6776

68-
const (
69-
rwMutexStateUnlocked = uint32(0)
70-
rwMutexStateWLocked = ^uint32(0)
71-
rwMutexMaxReaders = rwMutexStateWLocked - 1
72-
)
77+
const rwMutexMaxReaders = 1 << 30
7378

7479
func (rw *RWMutex) Lock() {
75-
if rw.state == 0 {
76-
// The mutex is completely unlocked.
77-
// Lock without waiting.
78-
rw.state = rwMutexStateWLocked
80+
// Exclusive lock for writers.
81+
rw.writerLock.Lock()
82+
83+
// Flag that we need to be awakened after the last read-lock unlocks.
84+
rw.writer.Store(1)
85+
86+
// Signal to readers that they can't lock this mutex anymore.
87+
n := uint32(rwMutexMaxReaders)
88+
waiting := rw.readers.Add(-n)
89+
if int32(waiting) == -rwMutexMaxReaders {
90+
// All readers were already unlocked, so we don't need to wait for them.
91+
rw.writer.Store(0)
7992
return
8093
}
8194

82-
// Wait for the lock to be released.
83-
rw.waitingWriters.Push(task.Current())
84-
task.Pause()
95+
// There is at least one reader.
96+
// Wait until all readers are unlocked. The last reader to unlock will set
97+
// rw.writer to 2 and awaken us.
98+
for rw.writer.Load() == 1 {
99+
rw.writer.Wait(1)
100+
}
101+
rw.writer.Store(0)
85102
}
86103

87104
func (rw *RWMutex) Unlock() {
88-
switch rw.state {
89-
case rwMutexStateWLocked:
90-
// This is correct.
91-
92-
case rwMutexStateUnlocked:
93-
// The mutex is already unlocked.
94-
panic("sync: unlock of unlocked RWMutex")
95-
96-
default:
97-
// The mutex is read-locked instead of write-locked.
98-
panic("sync: write-unlock of read-locked RWMutex")
105+
// Signal that new readers can lock this mutex.
106+
waiting := rw.readers.Add(rwMutexMaxReaders)
107+
if waiting != 0 {
108+
// Awaken all waiting readers.
109+
rw.readers.WakeAll()
99110
}
100111

101-
switch {
102-
case rw.maybeUnblockReaders():
103-
// Switched over to read mode.
104-
105-
case rw.maybeUnblockWriter():
106-
// Transferred to another writer.
107-
108-
default:
109-
// Nothing is waiting for the lock.
110-
rw.state = rwMutexStateUnlocked
111-
}
112+
// Done with this lock (next writer can try to get a lock).
113+
rw.writerLock.Unlock()
112114
}
113115

114116
func (rw *RWMutex) RLock() {
115-
if rw.state == rwMutexStateWLocked {
116-
// Wait for the write lock to be released.
117-
rw.waitingReaders.Push(task.Current())
118-
task.Pause()
119-
return
120-
}
117+
// Add us as a reader.
118+
newVal := rw.readers.Add(1)
121119

122-
if rw.state == rwMutexMaxReaders {
123-
panic("sync: too many readers on RWMutex")
120+
// Wait until the RWMutex is available for readers.
121+
for int32(newVal) <= 0 {
122+
rw.readers.Wait(newVal)
123+
newVal = rw.readers.Load()
124124
}
125-
126-
// Increase the reader count.
127-
rw.state++
128125
}
129126

130127
func (rw *RWMutex) RUnlock() {
131-
switch rw.state {
132-
case rwMutexStateUnlocked:
133-
// The mutex is already unlocked.
134-
panic("sync: unlock of unlocked RWMutex")
135-
136-
case rwMutexStateWLocked:
137-
// The mutex is write-locked instead of read-locked.
138-
panic("sync: read-unlock of write-locked RWMutex")
139-
}
140-
141-
rw.state--
128+
// Remove us as a reader.
129+
one := uint32(1)
130+
readers := int32(rw.readers.Add(-one))
142131

143-
if rw.state == rwMutexStateUnlocked {
144-
// This was the last reader.
145-
// Try to unblock a writer.
146-
rw.maybeUnblockWriter()
132+
// Check whether RUnlock was called too often.
133+
if readers == -1 || readers == (-rwMutexMaxReaders)-1 {
134+
runtimePanic("sync: RUnlock of unlocked RWMutex")
147135
}
148-
}
149136

150-
func (rw *RWMutex) maybeUnblockReaders() bool {
151-
var n uint32
152-
for {
153-
t := rw.waitingReaders.Pop()
154-
if t == nil {
155-
break
137+
if readers == -rwMutexMaxReaders {
138+
// This was the last read lock. Check whether we need to wake up a write
139+
// lock.
140+
if rw.writer.CompareAndSwap(1, 2) {
141+
rw.writer.Wake()
156142
}
157-
158-
n++
159-
scheduleTask(t)
160-
}
161-
if n == 0 {
162-
return false
163143
}
164-
165-
rw.state = n
166-
return true
167-
}
168-
169-
func (rw *RWMutex) maybeUnblockWriter() bool {
170-
t := rw.waitingWriters.Pop()
171-
if t == nil {
172-
return false
173-
}
174-
175-
rw.state = rwMutexStateWLocked
176-
scheduleTask(t)
177-
178-
return true
179144
}
180145

181146
type Locker interface {

0 commit comments

Comments
 (0)