1
0
mirror of https://github.com/golang/go synced 2024-11-26 07:47:57 -07:00

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 <josharian@gmail.com>
Trust: Sameer Ajmani <sameer@golang.org>
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
This commit is contained in:
Josh Bleecher Snyder 2021-01-29 15:41:18 -08:00
parent 691ac806d2
commit ae1fa08e41
2 changed files with 33 additions and 14 deletions

View File

@ -5,6 +5,7 @@
package context_test
import (
"context"
. "context"
"fmt"
"runtime"
@ -138,3 +139,17 @@ func BenchmarkCheckCanceled(b *testing.B) {
}
})
}
func BenchmarkContextCancelDone(b *testing.B) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
select {
case <-ctx.Done():
default:
}
}
})
}

View File

@ -303,10 +303,8 @@ func parentCancelCtx(parent Context) (*cancelCtx, bool) {
if !ok {
return nil, false
}
p.mu.Lock()
ok = p.done == done
p.mu.Unlock()
if !ok {
pdone, _ := p.done.Load().(chan struct{})
if pdone != done {
return nil, false
}
return p, true
@ -345,7 +343,7 @@ type cancelCtx struct {
Context
mu sync.Mutex // protects following fields
done chan struct{} // created lazily, closed by first cancel call
done atomic.Value // of chan struct{}, created lazily, closed by first cancel call
children map[canceler]struct{} // set to nil by the first cancel call
err error // set to non-nil by the first cancel call
}
@ -358,13 +356,18 @@ func (c *cancelCtx) Value(key interface{}) interface{} {
}
func (c *cancelCtx) Done() <-chan struct{} {
c.mu.Lock()
if c.done == nil {
c.done = make(chan struct{})
d := c.done.Load()
if d != nil {
return d.(chan struct{})
}
d := c.done
c.mu.Unlock()
return d
c.mu.Lock()
defer c.mu.Unlock()
d = c.done.Load()
if d == nil {
d = make(chan struct{})
c.done.Store(d)
}
return d.(chan struct{})
}
func (c *cancelCtx) Err() error {
@ -401,10 +404,11 @@ func (c *cancelCtx) cancel(removeFromParent bool, err error) {
return // already canceled
}
c.err = err
if c.done == nil {
c.done = closedchan
d, _ := c.done.Load().(chan struct{})
if d == nil {
c.done.Store(closedchan)
} else {
close(c.done)
close(d)
}
for child := range c.children {
// NOTE: acquiring the child's lock while holding parent's lock.