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  }