mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-16 10:43:29 +00:00
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.
This commit is contained in:
+62
-97
@@ -6,131 +6,96 @@ import (
|
|||||||
|
|
||||||
type Mutex = task.Mutex
|
type Mutex = task.Mutex
|
||||||
|
|
||||||
|
//go:linkname runtimePanic runtime.runtimePanic
|
||||||
|
func runtimePanic(msg string)
|
||||||
|
|
||||||
type RWMutex struct {
|
type RWMutex struct {
|
||||||
// waitingWriters are all of the tasks waiting for write locks.
|
// Reader count, with the number of readers that currently have read-locked
|
||||||
waitingWriters task.Stack
|
// this mutex.
|
||||||
|
// The value can be in two states: one where 0 means no readers and another
|
||||||
|
// where -rwMutexMaxReaders means no readers. A base of 0 is normal
|
||||||
|
// uncontended operation, a base of -rwMutexMaxReaders means a writer has
|
||||||
|
// the lock or is trying to get the lock. In the second case, readers should
|
||||||
|
// wait until the reader count becomes non-negative again to give the writer
|
||||||
|
// a chance to obtain the lock.
|
||||||
|
readers task.Futex
|
||||||
|
|
||||||
// waitingReaders are all of the tasks waiting for a read lock.
|
// Writer futex, normally 0. If there is a writer waiting until all readers
|
||||||
waitingReaders task.Stack
|
// have unlocked, this value is 1. It will be changed to a 2 (and get a
|
||||||
|
// wake) when the last reader unlocks.
|
||||||
|
writer task.Futex
|
||||||
|
|
||||||
// state is the current state of the RWMutex.
|
// Writer lock. Held between Lock() and Unlock().
|
||||||
// Iff the mutex is completely unlocked, it contains rwMutexStateUnlocked (aka 0).
|
writerLock Mutex
|
||||||
// Iff the mutex is write-locked, it contains rwMutexStateWLocked.
|
|
||||||
// While the mutex is read-locked, it contains the current number of readers.
|
|
||||||
state uint32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const rwMutexMaxReaders = 1 << 30
|
||||||
rwMutexStateUnlocked = uint32(0)
|
|
||||||
rwMutexStateWLocked = ^uint32(0)
|
|
||||||
rwMutexMaxReaders = rwMutexStateWLocked - 1
|
|
||||||
)
|
|
||||||
|
|
||||||
func (rw *RWMutex) Lock() {
|
func (rw *RWMutex) Lock() {
|
||||||
if rw.state == 0 {
|
// Exclusive lock for writers.
|
||||||
// The mutex is completely unlocked.
|
rw.writerLock.Lock()
|
||||||
// Lock without waiting.
|
|
||||||
rw.state = rwMutexStateWLocked
|
// Flag that we need to be awakened after the last read-lock unlocks.
|
||||||
|
rw.writer.Store(1)
|
||||||
|
|
||||||
|
// Signal to readers that they can't lock this mutex anymore.
|
||||||
|
n := uint32(rwMutexMaxReaders)
|
||||||
|
waiting := rw.readers.Add(-n)
|
||||||
|
if int32(waiting) == -rwMutexMaxReaders {
|
||||||
|
// All readers were already unlocked, so we don't need to wait for them.
|
||||||
|
rw.writer.Store(0)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for the lock to be released.
|
// There is at least one reader.
|
||||||
rw.waitingWriters.Push(task.Current())
|
// Wait until all readers are unlocked. The last reader to unlock will set
|
||||||
task.Pause()
|
// rw.writer to 2 and awaken us.
|
||||||
|
for rw.writer.Load() == 1 {
|
||||||
|
rw.writer.Wait(1)
|
||||||
|
}
|
||||||
|
rw.writer.Store(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rw *RWMutex) Unlock() {
|
func (rw *RWMutex) Unlock() {
|
||||||
switch rw.state {
|
// Signal that new readers can lock this mutex.
|
||||||
case rwMutexStateWLocked:
|
waiting := rw.readers.Add(rwMutexMaxReaders)
|
||||||
// This is correct.
|
if waiting != 0 {
|
||||||
|
// Awaken all waiting readers.
|
||||||
case rwMutexStateUnlocked:
|
rw.readers.WakeAll()
|
||||||
// The mutex is already unlocked.
|
|
||||||
panic("sync: unlock of unlocked RWMutex")
|
|
||||||
|
|
||||||
default:
|
|
||||||
// The mutex is read-locked instead of write-locked.
|
|
||||||
panic("sync: write-unlock of read-locked RWMutex")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
// Done with this lock (next writer can try to get a lock).
|
||||||
case rw.maybeUnblockReaders():
|
rw.writerLock.Unlock()
|
||||||
// Switched over to read mode.
|
|
||||||
|
|
||||||
case rw.maybeUnblockWriter():
|
|
||||||
// Transferred to another writer.
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Nothing is waiting for the lock.
|
|
||||||
rw.state = rwMutexStateUnlocked
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rw *RWMutex) RLock() {
|
func (rw *RWMutex) RLock() {
|
||||||
if rw.state == rwMutexStateWLocked {
|
// Add us as a reader.
|
||||||
// Wait for the write lock to be released.
|
newVal := rw.readers.Add(1)
|
||||||
rw.waitingReaders.Push(task.Current())
|
|
||||||
task.Pause()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if rw.state == rwMutexMaxReaders {
|
// Wait until the RWMutex is available for readers.
|
||||||
panic("sync: too many readers on RWMutex")
|
for int32(newVal) <= 0 {
|
||||||
|
rw.readers.Wait(newVal)
|
||||||
|
newVal = rw.readers.Load()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Increase the reader count.
|
|
||||||
rw.state++
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rw *RWMutex) RUnlock() {
|
func (rw *RWMutex) RUnlock() {
|
||||||
switch rw.state {
|
// Remove us as a reader.
|
||||||
case rwMutexStateUnlocked:
|
one := uint32(1)
|
||||||
// The mutex is already unlocked.
|
readers := int32(rw.readers.Add(-one))
|
||||||
panic("sync: unlock of unlocked RWMutex")
|
|
||||||
|
|
||||||
case rwMutexStateWLocked:
|
// Check whether RUnlock was called too often.
|
||||||
// The mutex is write-locked instead of read-locked.
|
if readers == -1 || readers == (-rwMutexMaxReaders)-1 {
|
||||||
panic("sync: read-unlock of write-locked RWMutex")
|
runtimePanic("sync: RUnlock of unlocked RWMutex")
|
||||||
}
|
}
|
||||||
|
|
||||||
rw.state--
|
if readers == -rwMutexMaxReaders {
|
||||||
|
// This was the last read lock. Check whether we need to wake up a write
|
||||||
if rw.state == rwMutexStateUnlocked {
|
// lock.
|
||||||
// This was the last reader.
|
if rw.writer.CompareAndSwap(1, 2) {
|
||||||
// Try to unblock a writer.
|
rw.writer.Wake()
|
||||||
rw.maybeUnblockWriter()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rw *RWMutex) maybeUnblockReaders() bool {
|
|
||||||
var n uint32
|
|
||||||
for {
|
|
||||||
t := rw.waitingReaders.Pop()
|
|
||||||
if t == nil {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
n++
|
|
||||||
scheduleTask(t)
|
|
||||||
}
|
}
|
||||||
if n == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
rw.state = n
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rw *RWMutex) maybeUnblockWriter() bool {
|
|
||||||
t := rw.waitingWriters.Pop()
|
|
||||||
if t == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
rw.state = rwMutexStateWLocked
|
|
||||||
scheduleTask(t)
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Locker interface {
|
type Locker interface {
|
||||||
|
|||||||
Reference in New Issue
Block a user