Skip to content

Commit 0b1e28a

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 8742feb commit 0b1e28a

File tree

1 file changed

+64
-99
lines changed

1 file changed

+64
-99
lines changed

src/sync/mutex.go

+64-99
Original file line numberDiff line numberDiff line change
@@ -6,131 +6,96 @@ import (
66

77
type Mutex = task.Mutex
88

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

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

23-
const (
24-
rwMutexStateUnlocked = uint32(0)
25-
rwMutexStateWLocked = ^uint32(0)
26-
rwMutexMaxReaders = rwMutexStateWLocked - 1
27-
)
32+
const rwMutexMaxReaders = 1 << 30
2833

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

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

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

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

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

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

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

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

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

136101
type Locker interface {

0 commit comments

Comments
 (0)