1
0
mirror of https://github.com/golang/go synced 2024-11-08 08:46:17 -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 package context_test
import ( import (
"context"
. "context" . "context"
"fmt" "fmt"
"runtime" "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 { if !ok {
return nil, false return nil, false
} }
p.mu.Lock() pdone, _ := p.done.Load().(chan struct{})
ok = p.done == done if pdone != done {
p.mu.Unlock()
if !ok {
return nil, false return nil, false
} }
return p, true return p, true
@ -345,7 +343,7 @@ type cancelCtx struct {
Context Context
mu sync.Mutex // protects following fields 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 children map[canceler]struct{} // set to nil by the first cancel call
err error // set to non-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{} { func (c *cancelCtx) Done() <-chan struct{} {
c.mu.Lock() d := c.done.Load()
if c.done == nil { if d != nil {
c.done = make(chan struct{}) return d.(chan struct{})
} }
d := c.done c.mu.Lock()
c.mu.Unlock() defer c.mu.Unlock()
return d d = c.done.Load()
if d == nil {
d = make(chan struct{})
c.done.Store(d)
}
return d.(chan struct{})
} }
func (c *cancelCtx) Err() error { func (c *cancelCtx) Err() error {
@ -401,10 +404,11 @@ func (c *cancelCtx) cancel(removeFromParent bool, err error) {
return // already canceled return // already canceled
} }
c.err = err c.err = err
if c.done == nil { d, _ := c.done.Load().(chan struct{})
c.done = closedchan if d == nil {
c.done.Store(closedchan)
} else { } else {
close(c.done) close(d)
} }
for child := range c.children { for child := range c.children {
// NOTE: acquiring the child's lock while holding parent's lock. // NOTE: acquiring the child's lock while holding parent's lock.