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.
5 // Package semaphore provides a weighted semaphore implementation.
6 package semaphore // import "golang.org/x/sync/semaphore"
16 ready chan<- struct{} // Closed when semaphore acquired.
19 // NewWeighted creates a new weighted semaphore with the given
20 // maximum combined weight for concurrent access.
21 func NewWeighted(n int64) *Weighted {
22 w := &Weighted{size: n}
26 // Weighted provides a way to bound concurrent access to a resource.
27 // The callers can request access with a given weight.
28 type Weighted struct {
35 // Acquire acquires the semaphore with a weight of n, blocking until resources
36 // are available or ctx is done. On success, returns nil. On failure, returns
37 // ctx.Err() and leaves the semaphore unchanged.
39 // If ctx is already done, Acquire may still succeed without blocking.
40 func (s *Weighted) Acquire(ctx context.Context, n int64) error {
42 if s.size-s.cur >= n && s.waiters.Len() == 0 {
49 // Don't make other Acquire calls block on one that's doomed to fail.
55 ready := make(chan struct{})
56 w := waiter{n: n, ready: ready}
57 elem := s.waiters.PushBack(w)
66 // Acquired the semaphore after we were canceled. Rather than trying to
67 // fix up the queue, just pretend we didn't notice the cancelation.
70 isFront := s.waiters.Front() == elem
71 s.waiters.Remove(elem)
72 // If we're at the front and there're extra tokens left, notify other waiters.
73 if isFront && s.size > s.cur {
85 // TryAcquire acquires the semaphore with a weight of n without blocking.
86 // On success, returns true. On failure, returns false and leaves the semaphore unchanged.
87 func (s *Weighted) TryAcquire(n int64) bool {
89 success := s.size-s.cur >= n && s.waiters.Len() == 0
97 // Release releases the semaphore with a weight of n.
98 func (s *Weighted) Release(n int64) {
103 panic("semaphore: released more than held")
109 func (s *Weighted) notifyWaiters() {
111 next := s.waiters.Front()
113 break // No more waiters blocked.
116 w := next.Value.(waiter)
117 if s.size-s.cur < w.n {
118 // Not enough tokens for the next waiter. We could keep going (to try to
119 // find a waiter with a smaller request), but under load that could cause
120 // starvation for large requests; instead, we leave all remaining waiters
123 // Consider a semaphore used as a read-write lock, with N tokens, N
124 // readers, and one writer. Each reader can Acquire(1) to obtain a read
125 // lock. The writer can Acquire(N) to obtain a write lock, excluding all
126 // of the readers. If we allow the readers to jump ahead in the queue,
127 // the writer will starve — there is always one token available for every
133 s.waiters.Remove(next)