100 lines
2.2 KiB
Go
100 lines
2.2 KiB
Go
|
package xsync
|
||
|
|
||
|
import (
|
||
|
"sync"
|
||
|
"sync/atomic"
|
||
|
)
|
||
|
|
||
|
// pool for P tokens
|
||
|
var ptokenPool sync.Pool
|
||
|
|
||
|
// a P token is used to point at the current OS thread (P)
|
||
|
// on which the goroutine is run; exact identity of the thread,
|
||
|
// as well as P migration tolerance, is not important since
|
||
|
// it's used to as a best effort mechanism for assigning
|
||
|
// concurrent operations (goroutines) to different stripes of
|
||
|
// the counter
|
||
|
type ptoken struct {
|
||
|
idx uint32
|
||
|
//lint:ignore U1000 prevents false sharing
|
||
|
pad [cacheLineSize - 4]byte
|
||
|
}
|
||
|
|
||
|
// A Counter is a striped int64 counter.
|
||
|
//
|
||
|
// Should be preferred over a single atomically updated int64
|
||
|
// counter in high contention scenarios.
|
||
|
//
|
||
|
// A Counter must not be copied after first use.
|
||
|
type Counter struct {
|
||
|
stripes []cstripe
|
||
|
mask uint32
|
||
|
}
|
||
|
|
||
|
type cstripe struct {
|
||
|
c int64
|
||
|
//lint:ignore U1000 prevents false sharing
|
||
|
pad [cacheLineSize - 8]byte
|
||
|
}
|
||
|
|
||
|
// NewCounter creates a new Counter instance.
|
||
|
func NewCounter() *Counter {
|
||
|
nstripes := nextPowOf2(parallelism())
|
||
|
c := Counter{
|
||
|
stripes: make([]cstripe, nstripes),
|
||
|
mask: nstripes - 1,
|
||
|
}
|
||
|
return &c
|
||
|
}
|
||
|
|
||
|
// Inc increments the counter by 1.
|
||
|
func (c *Counter) Inc() {
|
||
|
c.Add(1)
|
||
|
}
|
||
|
|
||
|
// Dec decrements the counter by 1.
|
||
|
func (c *Counter) Dec() {
|
||
|
c.Add(-1)
|
||
|
}
|
||
|
|
||
|
// Add adds the delta to the counter.
|
||
|
func (c *Counter) Add(delta int64) {
|
||
|
t, ok := ptokenPool.Get().(*ptoken)
|
||
|
if !ok {
|
||
|
t = new(ptoken)
|
||
|
t.idx = runtime_fastrand()
|
||
|
}
|
||
|
for {
|
||
|
stripe := &c.stripes[t.idx&c.mask]
|
||
|
cnt := atomic.LoadInt64(&stripe.c)
|
||
|
if atomic.CompareAndSwapInt64(&stripe.c, cnt, cnt+delta) {
|
||
|
break
|
||
|
}
|
||
|
// Give a try with another randomly selected stripe.
|
||
|
t.idx = runtime_fastrand()
|
||
|
}
|
||
|
ptokenPool.Put(t)
|
||
|
}
|
||
|
|
||
|
// Value returns the current counter value.
|
||
|
// The returned value may not include all of the latest operations in
|
||
|
// presence of concurrent modifications of the counter.
|
||
|
func (c *Counter) Value() int64 {
|
||
|
v := int64(0)
|
||
|
for i := 0; i < len(c.stripes); i++ {
|
||
|
stripe := &c.stripes[i]
|
||
|
v += atomic.LoadInt64(&stripe.c)
|
||
|
}
|
||
|
return v
|
||
|
}
|
||
|
|
||
|
// Reset resets the counter to zero.
|
||
|
// This method should only be used when it is known that there are
|
||
|
// no concurrent modifications of the counter.
|
||
|
func (c *Counter) Reset() {
|
||
|
for i := 0; i < len(c.stripes); i++ {
|
||
|
stripe := &c.stripes[i]
|
||
|
atomic.StoreInt64(&stripe.c, 0)
|
||
|
}
|
||
|
}
|