Problem Statement
Implement a counting semaphore with Acquire(n) and Release(n) operations that blocks if not enough permits are available.
Requirements
Acquire(n): Block until n permits are available, then consume themRelease(n): Return n permits to the poolTryAcquire(n): Non-blocking acquire, returns false if not enough permits- Thread-safe for concurrent access
Implementation with Channel
package semaphore
type Semaphore struct {
permits chan struct{}
}
func New(maxPermits int) *Semaphore {
s := &Semaphore{
permits: make(chan struct{}, maxPermits),
}
// Fill with permits
for i := 0; i < maxPermits; i++ {
s.permits <- struct{}{}
}
return s
}
func (s *Semaphore) Acquire() {
<-s.permits
}
func (s *Semaphore) AcquireN(n int) {
for i := 0; i < n; i++ {
<-s.permits
}
}
func (s *Semaphore) Release() {
s.permits <- struct{}{}
}
func (s *Semaphore) ReleaseN(n int) {
for i := 0; i < n; i++ {
s.permits <- struct{}{}
}
}
func (s *Semaphore) TryAcquire() bool {
select {
case <-s.permits:
return true
default:
return false
}
}Implementation with sync.Cond
package semaphore
import "sync"
type CondSemaphore struct {
permits int
max int
cond *sync.Cond
}
func NewCond(maxPermits int) *CondSemaphore {
return &CondSemaphore{
permits: maxPermits,
max: maxPermits,
cond: sync.NewCond(&sync.Mutex{}),
}
}
func (s *CondSemaphore) Acquire(n int) {
s.cond.L.Lock()
defer s.cond.L.Unlock()
for s.permits < n {
s.cond.Wait()
}
s.permits -= n
}
func (s *CondSemaphore) Release(n int) {
s.cond.L.Lock()
defer s.cond.L.Unlock()
s.permits += n
if s.permits > s.max {
s.permits = s.max
}
s.cond.Broadcast()
}Usage: Limit Concurrent Database Connections
var dbSem = semaphore.New(10) // Max 10 concurrent queries
func QueryDB(query string) (Result, error) {
dbSem.Acquire()
defer dbSem.Release()
return db.Query(query)
}Production Library
import "golang.org/x/sync/semaphore"
sem := semaphore.NewWeighted(10)
ctx := context.Background()
if err := sem.Acquire(ctx, 3); err != nil {
return err
}
defer sem.Release(3)