There are a number of under-the-hood implementations of the Context
interface in the Go standard library. For instance, the Background
and TODO
contexts are backed by the unexposed emptyCtx
type which is essentially just int
with some stub methods (proof). Similarly, every call to context.WithCancel()
returns an instance of the cancelCtx
type which is already a proper struct with a bunch of mutex-protected attributes (proof):
// A cancelCtx can be canceled. When canceled, it also cancels any children
// that implement canceler.
type cancelCtx struct {
Context
mu sync.Mutex // protects following fields
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
}
Why does the cancelCtx
struct use a mutually exclusive lock and not an RWLock
? For instance, the Err()
method currently acquires a full lock while it (probably) could have used just an RLock
:
func (c *cancelCtx) Err() error {
c.mu.Lock()
err := c.err
c.mu.Unlock()
return err
}
CodePudding user response:
One reason should be RWLock
has poor performance.
The performance of locks doesn't depends on the features it provides, it depends on the underlying implementation
. Although theoretically RWLock
can provides higher throughputs
, for this specific scenario (mutating a tiny variable), Mutex
could provide lower unnecessary overhead
.