github.com/zhiqiangxu/util@v0.0.0-20230112053021-0a7aee056cd5/deadlock/semaphore.go (about) 1 // Copyright 2017 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package deadlock provides a deadlock detector based on semaphore 6 package deadlock // import "golang.org/x/sync/semaphore" 7 8 import ( 9 "container/list" 10 "context" 11 "sync" 12 ) 13 14 type waiter struct { 15 n int64 16 ready chan<- struct{} // Closed when semaphore acquired. 17 } 18 19 type callback interface { 20 onAcquiredLocked(n int64) 21 onWaitLocked(n int64) 22 onWaitCanceledLocked(n int64) 23 onReleaseLocked(n int64) 24 } 25 26 // NewWeighted creates a new weighted semaphore with the given 27 // maximum combined weight for concurrent access. 28 func NewWeighted(n int64, cb callback) *Weighted { 29 w := &Weighted{size: n, cb: cb} 30 return w 31 } 32 33 // Weighted provides a way to bound concurrent access to a resource. 34 // The callers can request access with a given weight. 35 type Weighted struct { 36 size int64 37 cur int64 38 mu sync.Mutex 39 waiters list.List 40 cb callback 41 } 42 43 // Acquire acquires the semaphore with a weight of n, blocking until resources 44 // are available or ctx is done. On success, returns nil. On failure, returns 45 // ctx.Err() and leaves the semaphore unchanged. 46 // 47 // If ctx is already done, Acquire may still succeed without blocking. 48 func (s *Weighted) Acquire(ctx context.Context, n int64) error { 49 s.mu.Lock() 50 if s.size-s.cur >= n && s.waiters.Len() == 0 { 51 s.cur += n 52 s.cb.onAcquiredLocked(n) 53 s.mu.Unlock() 54 return nil 55 } 56 57 if n > s.size { 58 // Don't make other Acquire calls block on one that's doomed to fail. 59 s.mu.Unlock() 60 panic("semaphore: acquire more than size") 61 } 62 63 ready := make(chan struct{}) 64 w := waiter{n: n, ready: ready} 65 elem := s.waiters.PushBack(w) 66 s.cb.onWaitLocked(n) 67 s.mu.Unlock() 68 69 select { 70 case <-ctx.Done(): 71 err := ctx.Err() 72 s.mu.Lock() 73 select { 74 case <-ready: 75 // Acquired the semaphore after we were canceled. Rather than trying to 76 // fix up the queue, just pretend we didn't notice the cancelation. 77 err = nil 78 default: 79 isFront := s.waiters.Front() == elem 80 s.waiters.Remove(elem) 81 if isFront && s.size > s.cur { 82 s.notifyWaiters() 83 } 84 s.cb.onWaitCanceledLocked(n) 85 } 86 s.mu.Unlock() 87 return err 88 89 case <-ready: 90 return nil 91 } 92 } 93 94 // TryAcquire acquires the semaphore with a weight of n without blocking. 95 // On success, returns true. On failure, returns false and leaves the semaphore unchanged. 96 func (s *Weighted) TryAcquire(n int64) bool { 97 s.mu.Lock() 98 success := s.size-s.cur >= n && s.waiters.Len() == 0 99 if success { 100 s.cb.onAcquiredLocked(n) 101 s.cur += n 102 } 103 s.mu.Unlock() 104 return success 105 } 106 107 // Release releases the semaphore with a weight of n. 108 func (s *Weighted) Release(n int64) { 109 s.mu.Lock() 110 s.cur -= n 111 if s.cur < 0 { 112 s.mu.Unlock() 113 panic("semaphore: released more than held") 114 } 115 s.cb.onReleaseLocked(n) 116 s.notifyWaiters() 117 s.mu.Unlock() 118 } 119 120 func (s *Weighted) notifyWaiters() { 121 for { 122 next := s.waiters.Front() 123 if next == nil { 124 break // No more waiters blocked. 125 } 126 127 w := next.Value.(waiter) 128 if s.size-s.cur < w.n { 129 // Not enough tokens for the next waiter. We could keep going (to try to 130 // find a waiter with a smaller request), but under load that could cause 131 // starvation for large requests; instead, we leave all remaining waiters 132 // blocked. 133 // 134 // Consider a semaphore used as a read-write lock, with N tokens, N 135 // readers, and one writer. Each reader can Acquire(1) to obtain a read 136 // lock. The writer can Acquire(N) to obtain a write lock, excluding all 137 // of the readers. If we allow the readers to jump ahead in the queue, 138 // the writer will starve — there is always one token available for every 139 // reader. 140 break 141 } 142 143 s.cur += w.n 144 s.waiters.Remove(next) 145 s.cb.onAcquiredLocked(w.n) 146 close(w.ready) 147 } 148 }