github.com/sohaha/zlsgo@v1.7.13-0.20240501141223-10dd1a906f76/zsync/mutex.go (about) 1 package zsync 2 3 import ( 4 "runtime" 5 "sync" 6 "sync/atomic" 7 "time" 8 9 "github.com/sohaha/zlsgo/zstring" 10 ) 11 12 type ( 13 // RBMutex is a reader biased reader/writer mutual exclusion lock 14 RBMutex struct { 15 inhibitUntil time.Time 16 rslots []rslot 17 rw sync.RWMutex 18 rmask uint32 19 rbias int32 20 } 21 RToken struct { 22 slot uint32 23 pad [cacheLineSize - 4]byte 24 } 25 rslot struct { 26 mu int32 27 pad [cacheLineSize - 4]byte 28 } 29 ) 30 31 const nslowdown = 7 32 33 var rtokenPool sync.Pool 34 35 // NewRBMutex creates a new RBMutex instance 36 func NewRBMutex() *RBMutex { 37 nslots := nextPowOf2(parallelism()) 38 mu := RBMutex{ 39 rslots: make([]rslot, nslots), 40 rmask: nslots - 1, 41 rbias: 1, 42 } 43 return &mu 44 } 45 46 func (mu *RBMutex) RLock() *RToken { 47 if atomic.LoadInt32(&mu.rbias) == 1 { 48 t, ok := rtokenPool.Get().(*RToken) 49 if !ok { 50 t = &RToken{} 51 t.slot = zstring.RandUint32() 52 } 53 for i := 0; i < len(mu.rslots); i++ { 54 slot := t.slot + uint32(i) 55 rslot := &mu.rslots[slot&mu.rmask] 56 rslotmu := atomic.LoadInt32(&rslot.mu) 57 if atomic.CompareAndSwapInt32(&rslot.mu, rslotmu, rslotmu+1) { 58 if atomic.LoadInt32(&mu.rbias) == 1 { 59 t.slot = slot 60 return t 61 } 62 atomic.AddInt32(&rslot.mu, -1) 63 rtokenPool.Put(t) 64 break 65 } 66 } 67 } 68 69 mu.rw.RLock() 70 if atomic.LoadInt32(&mu.rbias) == 0 && time.Now().After(mu.inhibitUntil) { 71 atomic.StoreInt32(&mu.rbias, 1) 72 } 73 return nil 74 } 75 76 func (mu *RBMutex) RUnlock(t *RToken) { 77 if t == nil { 78 mu.rw.RUnlock() 79 return 80 } 81 if atomic.AddInt32(&mu.rslots[t.slot&mu.rmask].mu, -1) < 0 { 82 panic("invalid reader state detected") 83 } 84 rtokenPool.Put(t) 85 } 86 87 func (mu *RBMutex) Lock() { 88 mu.rw.Lock() 89 if atomic.LoadInt32(&mu.rbias) == 1 { 90 atomic.StoreInt32(&mu.rbias, 0) 91 start := time.Now() 92 for i := 0; i < len(mu.rslots); i++ { 93 for atomic.LoadInt32(&mu.rslots[i].mu) > 0 { 94 runtime.Gosched() 95 } 96 } 97 mu.inhibitUntil = time.Now().Add(time.Since(start) * nslowdown) 98 } 99 } 100 101 func (mu *RBMutex) Unlock() { 102 mu.rw.Unlock() 103 }