-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsemaphore.go
More file actions
60 lines (54 loc) · 1.57 KB
/
Copy pathsemaphore.go
File metadata and controls
60 lines (54 loc) · 1.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package syncx
import "context"
// Semaphore limits concurrent access to a finite number of slots. Use it
// to bound how many goroutines touch a scarce resource at once — database
// connections, memory-heavy work, calls to a rate-sensitive upstream —
// while letting any number of goroutines queue for access:
//
// sem := syncx.NewSemaphore(10)
// if err := sem.Acquire(ctx); err != nil {
// return err
// }
// defer sem.Release()
//
// A Semaphore is safe for concurrent use and must not be copied.
type Semaphore struct {
slots chan struct{}
}
// NewSemaphore creates a semaphore with max concurrent slots. Values of max
// below 1 are treated as 1.
func NewSemaphore(max int) *Semaphore {
if max <= 0 {
max = 1
}
return &Semaphore{slots: make(chan struct{}, max)}
}
// Acquire blocks until a slot is available or ctx is cancelled, in which
// case it returns ctx.Err() without holding a slot.
func (s *Semaphore) Acquire(ctx context.Context) error {
select {
case s.slots <- struct{}{}:
return nil
case <-ctx.Done():
return ctx.Err()
}
}
// TryAcquire attempts to acquire a slot without blocking and reports
// whether it succeeded. Use it to shed load instead of queueing.
func (s *Semaphore) TryAcquire() bool {
select {
case s.slots <- struct{}{}:
return true
default:
return false
}
}
// Release frees one slot previously obtained with Acquire or TryAcquire.
// Calling it without holding a slot is a no-op rather than an error, so a
// stray defer cannot corrupt the semaphore's state.
func (s *Semaphore) Release() {
select {
case <-s.slots:
default:
}
}