Skip to content

Commit ae1fa08

Browse files
committed
context: reduce contention in cancelCtx.Done
Use an atomic.Value to hold the done channel. Conveniently, we have a mutex handy to coordinate writes to it. name old time/op new time/op delta ContextCancelDone-8 67.5ns ±10% 2.2ns ±11% -96.74% (p=0.000 n=30+28) Fixes #42564 Change-Id: I5d72e0e87fb221d4e230209e5fb4698bea4053c6 Reviewed-on: https://go-review.googlesource.com/c/go/+/288193 Trust: Josh Bleecher Snyder <[email protected]> Trust: Sameer Ajmani <[email protected]> Run-TryBot: Josh Bleecher Snyder <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Bryan C. Mills <[email protected]>
1 parent 691ac80 commit ae1fa08

File tree

2 files changed

+32
-13
lines changed

2 files changed

+32
-13
lines changed

src/context/benchmark_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package context_test
66

77
import (
8+
"context"
89
. "context"
910
"fmt"
1011
"runtime"
@@ -138,3 +139,17 @@ func BenchmarkCheckCanceled(b *testing.B) {
138139
}
139140
})
140141
}
142+
143+
func BenchmarkContextCancelDone(b *testing.B) {
144+
ctx, cancel := context.WithCancel(context.Background())
145+
defer cancel()
146+
147+
b.RunParallel(func(pb *testing.PB) {
148+
for pb.Next() {
149+
select {
150+
case <-ctx.Done():
151+
default:
152+
}
153+
}
154+
})
155+
}

src/context/context.go

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -303,10 +303,8 @@ func parentCancelCtx(parent Context) (*cancelCtx, bool) {
303303
if !ok {
304304
return nil, false
305305
}
306-
p.mu.Lock()
307-
ok = p.done == done
308-
p.mu.Unlock()
309-
if !ok {
306+
pdone, _ := p.done.Load().(chan struct{})
307+
if pdone != done {
310308
return nil, false
311309
}
312310
return p, true
@@ -345,7 +343,7 @@ type cancelCtx struct {
345343
Context
346344

347345
mu sync.Mutex // protects following fields
348-
done chan struct{} // created lazily, closed by first cancel call
346+
done atomic.Value // of chan struct{}, created lazily, closed by first cancel call
349347
children map[canceler]struct{} // set to nil by the first cancel call
350348
err error // set to non-nil by the first cancel call
351349
}
@@ -358,13 +356,18 @@ func (c *cancelCtx) Value(key interface{}) interface{} {
358356
}
359357

360358
func (c *cancelCtx) Done() <-chan struct{} {
359+
d := c.done.Load()
360+
if d != nil {
361+
return d.(chan struct{})
362+
}
361363
c.mu.Lock()
362-
if c.done == nil {
363-
c.done = make(chan struct{})
364+
defer c.mu.Unlock()
365+
d = c.done.Load()
366+
if d == nil {
367+
d = make(chan struct{})
368+
c.done.Store(d)
364369
}
365-
d := c.done
366-
c.mu.Unlock()
367-
return d
370+
return d.(chan struct{})
368371
}
369372

370373
func (c *cancelCtx) Err() error {
@@ -401,10 +404,11 @@ func (c *cancelCtx) cancel(removeFromParent bool, err error) {
401404
return // already canceled
402405
}
403406
c.err = err
404-
if c.done == nil {
405-
c.done = closedchan
407+
d, _ := c.done.Load().(chan struct{})
408+
if d == nil {
409+
c.done.Store(closedchan)
406410
} else {
407-
close(c.done)
411+
close(d)
408412
}
409413
for child := range c.children {
410414
// NOTE: acquiring the child's lock while holding parent's lock.

0 commit comments

Comments
 (0)