Skip to content

Commit 0556e26

Browse files
dvyukovrsc
authored andcommittedFeb 17, 2017
sync: make Mutex more fair
Add new starvation mode for Mutex. In starvation mode ownership is directly handed off from unlocking goroutine to the next waiter. New arriving goroutines don't compete for ownership. Unfair wait time is now limited to 1ms. Also fix a long standing bug that goroutines were requeued at the tail of the wait queue. That lead to even more unfair acquisition times with multiple waiters. Performance of normal mode is not considerably affected. Fixes #13086 On the provided in the issue lockskew program: done in 1.207853ms done in 1.177451ms done in 1.184168ms done in 1.198633ms done in 1.185797ms done in 1.182502ms done in 1.316485ms done in 1.211611ms done in 1.182418ms name old time/op new time/op delta MutexUncontended-48 0.65ns ± 0% 0.65ns ± 1% ~ (p=0.087 n=10+10) Mutex-48 112ns ± 1% 114ns ± 1% +1.69% (p=0.000 n=10+10) MutexSlack-48 113ns ± 0% 87ns ± 1% -22.65% (p=0.000 n=8+10) MutexWork-48 149ns ± 0% 145ns ± 0% -2.48% (p=0.000 n=9+10) MutexWorkSlack-48 149ns ± 0% 122ns ± 3% -18.26% (p=0.000 n=6+10) MutexNoSpin-48 103ns ± 4% 105ns ± 3% ~ (p=0.089 n=10+10) MutexSpin-48 490ns ± 4% 515ns ± 6% +5.08% (p=0.006 n=10+10) Cond32-48 13.4µs ± 6% 13.1µs ± 5% -2.75% (p=0.023 n=10+10) RWMutexWrite100-48 53.2ns ± 3% 41.2ns ± 3% -22.57% (p=0.000 n=10+10) RWMutexWrite10-48 45.9ns ± 2% 43.9ns ± 2% -4.38% (p=0.000 n=10+10) RWMutexWorkWrite100-48 122ns ± 2% 134ns ± 1% +9.92% (p=0.000 n=10+10) RWMutexWorkWrite10-48 206ns ± 1% 188ns ± 1% -8.52% (p=0.000 n=8+10) Cond32-24 12.1µs ± 3% 12.4µs ± 3% +1.98% (p=0.043 n=10+9) MutexUncontended-24 0.74ns ± 1% 0.75ns ± 1% ~ (p=0.650 n=10+10) Mutex-24 122ns ± 2% 124ns ± 1% +1.31% (p=0.007 n=10+10) MutexSlack-24 96.9ns ± 2% 102.8ns ± 2% +6.11% (p=0.000 n=10+10) MutexWork-24 146ns ± 1% 135ns ± 2% -7.70% (p=0.000 n=10+9) MutexWorkSlack-24 135ns ± 1% 128ns ± 2% -5.01% (p=0.000 n=10+9) MutexNoSpin-24 114ns ± 3% 110ns ± 4% -3.84% (p=0.000 n=10+10) MutexSpin-24 482ns ± 4% 475ns ± 8% ~ (p=0.286 n=10+10) RWMutexWrite100-24 43.0ns ± 3% 43.1ns ± 2% ~ (p=0.956 n=10+10) RWMutexWrite10-24 43.4ns ± 1% 43.2ns ± 1% ~ (p=0.085 n=10+9) RWMutexWorkWrite100-24 130ns ± 3% 131ns ± 3% ~ (p=0.747 n=10+10) RWMutexWorkWrite10-24 191ns ± 1% 192ns ± 1% ~ (p=0.210 n=10+10) Cond32-12 11.5µs ± 2% 11.7µs ± 2% +1.98% (p=0.002 n=10+10) MutexUncontended-12 1.48ns ± 0% 1.50ns ± 1% +1.08% (p=0.004 n=10+10) Mutex-12 141ns ± 1% 143ns ± 1% +1.63% (p=0.000 n=10+10) MutexSlack-12 121ns ± 0% 119ns ± 0% -1.65% (p=0.001 n=8+9) MutexWork-12 141ns ± 2% 150ns ± 3% +6.36% (p=0.000 n=9+10) MutexWorkSlack-12 131ns ± 0% 138ns ± 0% +5.73% (p=0.000 n=9+10) MutexNoSpin-12 87.0ns ± 1% 83.7ns ± 1% -3.80% (p=0.000 n=10+10) MutexSpin-12 364ns ± 1% 377ns ± 1% +3.77% (p=0.000 n=10+10) RWMutexWrite100-12 42.8ns ± 1% 43.9ns ± 1% +2.41% (p=0.000 n=8+10) RWMutexWrite10-12 39.8ns ± 4% 39.3ns ± 1% ~ (p=0.433 n=10+9) RWMutexWorkWrite100-12 131ns ± 1% 131ns ± 0% ~ (p=0.591 n=10+9) RWMutexWorkWrite10-12 173ns ± 1% 174ns ± 0% ~ (p=0.059 n=10+8) Cond32-6 10.9µs ± 2% 10.9µs ± 2% ~ (p=0.739 n=10+10) MutexUncontended-6 2.97ns ± 0% 2.97ns ± 0% ~ (all samples are equal) Mutex-6 122ns ± 6% 122ns ± 2% ~ (p=0.668 n=10+10) MutexSlack-6 149ns ± 3% 142ns ± 3% -4.63% (p=0.000 n=10+10) MutexWork-6 136ns ± 3% 140ns ± 5% ~ (p=0.077 n=10+10) MutexWorkSlack-6 152ns ± 0% 138ns ± 2% -9.21% (p=0.000 n=6+10) MutexNoSpin-6 150ns ± 1% 152ns ± 0% +1.50% (p=0.000 n=8+10) MutexSpin-6 726ns ± 0% 730ns ± 1% ~ (p=0.069 n=10+10) RWMutexWrite100-6 40.6ns ± 1% 40.9ns ± 1% +0.91% (p=0.001 n=8+10) RWMutexWrite10-6 37.1ns ± 0% 37.0ns ± 1% ~ (p=0.386 n=9+10) RWMutexWorkWrite100-6 133ns ± 1% 134ns ± 1% +1.01% (p=0.005 n=9+10) RWMutexWorkWrite10-6 152ns ± 0% 152ns ± 0% ~ (all samples are equal) Cond32-2 7.86µs ± 2% 7.95µs ± 2% +1.10% (p=0.023 n=10+10) MutexUncontended-2 8.10ns ± 0% 9.11ns ± 4% +12.44% (p=0.000 n=9+10) Mutex-2 32.9ns ± 9% 38.4ns ± 6% +16.58% (p=0.000 n=10+10) MutexSlack-2 93.4ns ± 1% 98.5ns ± 2% +5.39% (p=0.000 n=10+9) MutexWork-2 40.8ns ± 3% 43.8ns ± 7% +7.38% (p=0.000 n=10+9) MutexWorkSlack-2 98.6ns ± 5% 108.2ns ± 2% +9.80% (p=0.000 n=10+8) MutexNoSpin-2 399ns ± 1% 398ns ± 2% ~ (p=0.463 n=8+9) MutexSpin-2 1.99µs ± 3% 1.97µs ± 1% -0.81% (p=0.003 n=9+8) RWMutexWrite100-2 37.6ns ± 5% 46.0ns ± 4% +22.17% (p=0.000 n=10+8) RWMutexWrite10-2 50.1ns ± 6% 36.8ns ±12% -26.46% (p=0.000 n=9+10) RWMutexWorkWrite100-2 136ns ± 0% 134ns ± 2% -1.80% (p=0.001 n=7+9) RWMutexWorkWrite10-2 140ns ± 1% 138ns ± 1% -1.50% (p=0.000 n=10+10) Cond32 5.93µs ± 1% 5.91µs ± 0% ~ (p=0.411 n=9+10) MutexUncontended 15.9ns ± 0% 15.8ns ± 0% -0.63% (p=0.000 n=8+8) Mutex 15.9ns ± 0% 15.8ns ± 0% -0.44% (p=0.003 n=10+10) MutexSlack 26.9ns ± 3% 26.7ns ± 2% ~ (p=0.084 n=10+10) MutexWork 47.8ns ± 0% 47.9ns ± 0% +0.21% (p=0.014 n=9+8) MutexWorkSlack 54.9ns ± 3% 54.5ns ± 3% ~ (p=0.254 n=10+10) MutexNoSpin 786ns ± 2% 765ns ± 1% -2.66% (p=0.000 n=10+10) MutexSpin 3.87µs ± 1% 3.83µs ± 0% -0.85% (p=0.005 n=9+8) RWMutexWrite100 21.2ns ± 2% 21.0ns ± 1% -0.88% (p=0.018 n=10+9) RWMutexWrite10 22.6ns ± 1% 22.6ns ± 0% ~ (p=0.471 n=9+9) RWMutexWorkWrite100 132ns ± 0% 132ns ± 0% ~ (all samples are equal) RWMutexWorkWrite10 124ns ± 0% 123ns ± 0% ~ (p=0.656 n=10+10) Change-Id: I66412a3a0980df1233ad7a5a0cd9723b4274528b Reviewed-on: https://go-review.googlesource.com/34310 Run-TryBot: Russ Cox <rsc@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Russ Cox <rsc@golang.org>
1 parent 79f6a5c commit 0556e26

File tree

10 files changed

+231
-63
lines changed

10 files changed

+231
-63
lines changed
 

‎src/runtime/mgc.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -953,7 +953,7 @@ func gcStart(mode gcMode, forceTrigger bool) {
953953
// another thread.
954954
useStartSema := mode == gcBackgroundMode
955955
if useStartSema {
956-
semacquire(&work.startSema, 0)
956+
semacquire(&work.startSema)
957957
// Re-check transition condition under transition lock.
958958
if !gcShouldStart(forceTrigger) {
959959
semrelease(&work.startSema)
@@ -977,7 +977,7 @@ func gcStart(mode gcMode, forceTrigger bool) {
977977
}
978978

979979
// Ok, we're doing it! Stop everybody else
980-
semacquire(&worldsema, 0)
980+
semacquire(&worldsema)
981981

982982
if trace.enabled {
983983
traceGCStart()
@@ -1087,7 +1087,7 @@ func gcStart(mode gcMode, forceTrigger bool) {
10871087
// by mark termination.
10881088
func gcMarkDone() {
10891089
top:
1090-
semacquire(&work.markDoneSema, 0)
1090+
semacquire(&work.markDoneSema)
10911091

10921092
// Re-check transition condition under transition lock.
10931093
if !(gcphase == _GCmark && work.nwait == work.nproc && !gcMarkWorkAvailable(nil)) {

‎src/runtime/proc.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -928,7 +928,7 @@ func restartg(gp *g) {
928928
// in panic or being exited, this may not reliably stop all
929929
// goroutines.
930930
func stopTheWorld(reason string) {
931-
semacquire(&worldsema, 0)
931+
semacquire(&worldsema)
932932
getg().m.preemptoff = reason
933933
systemstack(stopTheWorldWithSema)
934934
}

‎src/runtime/sema.go

Lines changed: 64 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -53,22 +53,22 @@ var semtable [semTabSize]struct {
5353

5454
//go:linkname sync_runtime_Semacquire sync.runtime_Semacquire
5555
func sync_runtime_Semacquire(addr *uint32) {
56-
semacquire(addr, semaBlockProfile)
56+
semacquire1(addr, false, semaBlockProfile)
5757
}
5858

5959
//go:linkname poll_runtime_Semacquire internal/poll.runtime_Semacquire
6060
func poll_runtime_Semacquire(addr *uint32) {
61-
semacquire(addr, semaBlockProfile)
61+
semacquire1(addr, false, semaBlockProfile)
6262
}
6363

6464
//go:linkname sync_runtime_Semrelease sync.runtime_Semrelease
65-
func sync_runtime_Semrelease(addr *uint32) {
66-
semrelease(addr)
65+
func sync_runtime_Semrelease(addr *uint32, handoff bool) {
66+
semrelease1(addr, handoff)
6767
}
6868

6969
//go:linkname sync_runtime_SemacquireMutex sync.runtime_SemacquireMutex
70-
func sync_runtime_SemacquireMutex(addr *uint32) {
71-
semacquire(addr, semaBlockProfile|semaMutexProfile)
70+
func sync_runtime_SemacquireMutex(addr *uint32, lifo bool) {
71+
semacquire1(addr, lifo, semaBlockProfile|semaMutexProfile)
7272
}
7373

7474
//go:linkname poll_runtime_Semrelease internal/poll.runtime_Semrelease
@@ -91,7 +91,11 @@ const (
9191
)
9292

9393
// Called from runtime.
94-
func semacquire(addr *uint32, profile semaProfileFlags) {
94+
func semacquire(addr *uint32) {
95+
semacquire1(addr, false, 0)
96+
}
97+
98+
func semacquire1(addr *uint32, lifo bool, profile semaProfileFlags) {
9599
gp := getg()
96100
if gp != gp.m.curg {
97101
throw("semacquire not on the G stack")
@@ -113,6 +117,7 @@ func semacquire(addr *uint32, profile semaProfileFlags) {
113117
t0 := int64(0)
114118
s.releasetime = 0
115119
s.acquiretime = 0
120+
s.ticket = 0
116121
if profile&semaBlockProfile != 0 && blockprofilerate > 0 {
117122
t0 = cputicks()
118123
s.releasetime = -1
@@ -135,9 +140,9 @@ func semacquire(addr *uint32, profile semaProfileFlags) {
135140
}
136141
// Any semrelease after the cansemacquire knows we're waiting
137142
// (we set nwait above), so go to sleep.
138-
root.queue(addr, s)
143+
root.queue(addr, s, lifo)
139144
goparkunlock(&root.lock, "semacquire", traceEvGoBlockSync, 4)
140-
if cansemacquire(addr) {
145+
if s.ticket != 0 || cansemacquire(addr) {
141146
break
142147
}
143148
}
@@ -148,6 +153,10 @@ func semacquire(addr *uint32, profile semaProfileFlags) {
148153
}
149154

150155
func semrelease(addr *uint32) {
156+
semrelease1(addr, false)
157+
}
158+
159+
func semrelease1(addr *uint32, handoff bool) {
151160
root := semroot(addr)
152161
atomic.Xadd(addr, 1)
153162

@@ -173,6 +182,12 @@ func semrelease(addr *uint32) {
173182
unlock(&root.lock)
174183
if s != nil { // May be slow, so unlock first
175184
acquiretime := s.acquiretime
185+
if s.ticket != 0 {
186+
throw("corrupted semaphore ticket")
187+
}
188+
if handoff && cansemacquire(addr) {
189+
s.ticket = 1
190+
}
176191
readyWithTime(s, 5)
177192
if acquiretime != 0 {
178193
mutexevent(t0-acquiretime, 3)
@@ -197,7 +212,7 @@ func cansemacquire(addr *uint32) bool {
197212
}
198213

199214
// queue adds s to the blocked goroutines in semaRoot.
200-
func (root *semaRoot) queue(addr *uint32, s *sudog) {
215+
func (root *semaRoot) queue(addr *uint32, s *sudog, lifo bool) {
201216
s.g = getg()
202217
s.elem = unsafe.Pointer(addr)
203218
s.next = nil
@@ -207,14 +222,41 @@ func (root *semaRoot) queue(addr *uint32, s *sudog) {
207222
pt := &root.treap
208223
for t := *pt; t != nil; t = *pt {
209224
if t.elem == unsafe.Pointer(addr) {
210-
// Already have addr in list; add s to end of per-addr list.
211-
if t.waittail == nil {
212-
t.waitlink = s
225+
// Already have addr in list.
226+
if lifo {
227+
// Substitute s in t's place in treap.
228+
*pt = s
229+
s.ticket = t.ticket
230+
s.acquiretime = t.acquiretime
231+
s.parent = t.parent
232+
s.prev = t.prev
233+
s.next = t.next
234+
if s.prev != nil {
235+
s.prev.parent = s
236+
}
237+
if s.next != nil {
238+
s.next.parent = s
239+
}
240+
// Add t first in s's wait list.
241+
s.waitlink = t
242+
s.waittail = t.waittail
243+
if s.waittail == nil {
244+
s.waittail = t
245+
}
246+
t.parent = nil
247+
t.prev = nil
248+
t.next = nil
249+
t.waittail = nil
213250
} else {
214-
t.waittail.waitlink = s
251+
// Add s to end of t's wait list.
252+
if t.waittail == nil {
253+
t.waitlink = s
254+
} else {
255+
t.waittail.waitlink = s
256+
}
257+
t.waittail = s
258+
s.waitlink = nil
215259
}
216-
t.waittail = s
217-
s.waitlink = nil
218260
return
219261
}
220262
last = t
@@ -319,6 +361,7 @@ Found:
319361
s.elem = nil
320362
s.next = nil
321363
s.prev = nil
364+
s.ticket = 0
322365
return s, now
323366
}
324367

@@ -561,3 +604,8 @@ func notifyListCheck(sz uintptr) {
561604
throw("bad notifyList size")
562605
}
563606
}
607+
608+
//go:linkname sync_nanotime sync.runtime_nanotime
609+
func sync_nanotime() int64 {
610+
return nanotime()
611+
}

‎src/runtime/trace.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ func StopTrace() {
313313

314314
// The world is started but we've set trace.shutdown, so new tracing can't start.
315315
// Wait for the trace reader to flush pending buffers and stop.
316-
semacquire(&trace.shutdownSema, 0)
316+
semacquire(&trace.shutdownSema)
317317
if raceenabled {
318318
raceacquire(unsafe.Pointer(&trace.shutdownSema))
319319
}

‎src/sync/mutex.go

Lines changed: 116 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,34 @@ type Locker interface {
3737
const (
3838
mutexLocked = 1 << iota // mutex is locked
3939
mutexWoken
40+
mutexStarving
4041
mutexWaiterShift = iota
42+
43+
// Mutex fairness.
44+
//
45+
// Mutex can be in 2 modes of operations: normal and starvation.
46+
// In normal mode waiters are queued in FIFO order, but a woken up waiter
47+
// does not own the mutex and competes with new arriving goroutines over
48+
// the ownership. New arriving goroutines have an advantage -- they are
49+
// already running on CPU and there can be lots of them, so a woken up
50+
// waiter has good chances of losing. In such case it is queued at front
51+
// of the wait queue. If a waiter fails to acquire the mutex for more than 1ms,
52+
// it switches mutex to the starvation mode.
53+
//
54+
// In starvation mode ownership of the mutex is directly handed off from
55+
// the unlocking goroutine to the waiter at the front of the queue.
56+
// New arriving goroutines don't try to acquire the mutex even if it appears
57+
// to be unlocked, and don't try to spin. Instead they queue themselves at
58+
// the tail of the wait queue.
59+
//
60+
// If a waiter receives ownership of the mutex and sees that either
61+
// (1) it is the last waiter in the queue, or (2) it waited for less than 1 ms,
62+
// it switches mutex back to normal operation mode.
63+
//
64+
// Normal mode has considerably better performance as a goroutine can acquire
65+
// a mutex several times in a row even if there are blocked waiters.
66+
// Starvation mode is important to prevent pathological cases of tail latency.
67+
starvationThresholdNs = 1e6
4168
)
4269

4370
// Lock locks m.
@@ -52,41 +79,86 @@ func (m *Mutex) Lock() {
5279
return
5380
}
5481

82+
var waitStartTime int64
83+
starving := false
5584
awoke := false
5685
iter := 0
86+
old := m.state
5787
for {
58-
old := m.state
59-
new := old | mutexLocked
60-
if old&mutexLocked != 0 {
61-
if runtime_canSpin(iter) {
62-
// Active spinning makes sense.
63-
// Try to set mutexWoken flag to inform Unlock
64-
// to not wake other blocked goroutines.
65-
if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&
66-
atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {
67-
awoke = true
68-
}
69-
runtime_doSpin()
70-
iter++
71-
continue
88+
// Don't spin in starvation mode, ownership is handed off to waiters
89+
// so we won't be able to acquire the mutex anyway.
90+
if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {
91+
// Active spinning makes sense.
92+
// Try to set mutexWoken flag to inform Unlock
93+
// to not wake other blocked goroutines.
94+
if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&
95+
atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {
96+
awoke = true
7297
}
73-
new = old + 1<<mutexWaiterShift
98+
runtime_doSpin()
99+
iter++
100+
old = m.state
101+
continue
102+
}
103+
new := old
104+
// Don't try to acquire starving mutex, new arriving goroutines must queue.
105+
if old&mutexStarving == 0 {
106+
new |= mutexLocked
107+
}
108+
if old&(mutexLocked|mutexStarving) != 0 {
109+
new += 1 << mutexWaiterShift
110+
}
111+
// The current goroutine switches mutex to starvation mode.
112+
// But if the mutex is currently unlocked, don't do the switch.
113+
// Unlock expects that starving mutex has waiters, which will not
114+
// be true in this case.
115+
if starving && old&mutexLocked != 0 {
116+
new |= mutexStarving
74117
}
75118
if awoke {
76119
// The goroutine has been woken from sleep,
77120
// so we need to reset the flag in either case.
78121
if new&mutexWoken == 0 {
79-
throw("sync: inconsistent mutex state")
122+
panic("sync: inconsistent mutex state")
80123
}
81124
new &^= mutexWoken
82125
}
83126
if atomic.CompareAndSwapInt32(&m.state, old, new) {
84-
if old&mutexLocked == 0 {
127+
if old&(mutexLocked|mutexStarving) == 0 {
128+
break // locked the mutex with CAS
129+
}
130+
// If we were already waiting before, queue at the front of the queue.
131+
queueLifo := waitStartTime != 0
132+
if waitStartTime == 0 {
133+
waitStartTime = runtime_nanotime()
134+
}
135+
runtime_SemacquireMutex(&m.sema, queueLifo)
136+
starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNs
137+
old = m.state
138+
if old&mutexStarving != 0 {
139+
// If this goroutine was woken and mutex is in starvation mode,
140+
// ownership was handed off to us but mutex is in somewhat
141+
// inconsistent state: mutexLocked is not set and we are still
Has comments. Original line has comments.
142+
// accounted as waiter. Fix that.
143+
if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 {
144+
panic("sync: inconsistent mutex state")
145+
}
146+
delta := int32(mutexLocked - 1<<mutexWaiterShift)
147+
if !starving || old>>mutexWaiterShift == 1 {
148+
// Exit starvation mode.
149+
// Critical to do it here and consider wait time.
150+
// Starvation mode is so inefficient, that two goroutines
151+
// can go lock-step infinitely once they switch mutex
152+
// to starvation mode.
153+
delta -= mutexStarving
154+
}
155+
atomic.AddInt32(&m.state, delta)
85156
break
86157
}
87-
runtime_SemacquireMutex(&m.sema)
88158
awoke = true
89159
iter = 0
160+
} else {
161+
old = m.state
90162
}
91163
}
92164

@@ -110,22 +182,33 @@ func (m *Mutex) Unlock() {
110182
// Fast path: drop lock bit.
111183
new := atomic.AddInt32(&m.state, -mutexLocked)
112184
if (new+mutexLocked)&mutexLocked == 0 {
113-
throw("sync: unlock of unlocked mutex")
185+
panic("sync: unlock of unlocked mutex")
114186
}
115-
116-
old := new
117-
for {
118-
// If there are no waiters or a goroutine has already
119-
// been woken or grabbed the lock, no need to wake anyone.
120-
if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken) != 0 {
121-
return
122-
}
123-
// Grab the right to wake someone.
124-
new = (old - 1<<mutexWaiterShift) | mutexWoken
125-
if atomic.CompareAndSwapInt32(&m.state, old, new) {
126-
runtime_Semrelease(&m.sema)
127-
return
187+
if new&mutexStarving == 0 {
188+
old := new
189+
for {
190+
// If there are no waiters or a goroutine has already
191+
// been woken or grabbed the lock, no need to wake anyone.
192+
// In starvation mode ownership is directly handed off from unlocking
193+
// goroutine to the next waiter. We are not part of this chain,
194+
// since we did not observe mutexStarving when we unlocked the mutex above.
195+
// So get off the way.
196+
if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 {
197+
return
198+
}
199+
// Grab the right to wake someone.
200+
new = (old - 1<<mutexWaiterShift) | mutexWoken
201+
if atomic.CompareAndSwapInt32(&m.state, old, new) {
202+
runtime_Semrelease(&m.sema, false)
203+
return
204+
}
205+
old = m.state
128206
}
129-
old = m.state
207+
} else {
208+
// Starving mode: handoff mutex ownership to the next waiter.
209+
// Note: mutexLocked is not set, the waiter will set it after wakeup.
210+
// But mutex is still considered locked if mutexStarving is set,
211+
// so new coming goroutines won't acquire it.
212+
runtime_Semrelease(&m.sema, true)
130213
}
131214
}

0 commit comments

Comments
 (0)
Please sign in to comment.