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  }