/go-semaphore

Simple implementation of the classic semaphore construct using Go's sync primitives

go-semaphore

Simple implementation of the classic semaphore construct using Go's sync primitives

package semaphore

import (
	"sync"
)

type Semaphore struct {
	cnt  int
	cond *sync.Cond
}

func (s *Semaphore) V() {
	s.cond.L.Lock()
	s.cnt++
	s.cond.Signal()
	s.cond.L.Unlock()
}

func (s *Semaphore) P() {
	s.cond.L.Lock()

	for done := false; !done; {
		if s.cnt > 0 {
			s.cnt--
			done = true
		} else {
			s.cond.Wait()
		}
	}
	s.cond.L.Unlock()
}

func NewSemaphore(cnt int) *Semaphore {
	s := new(Semaphore)

	s.cond = sync.NewCond(new(sync.Mutex))
	s.cnt = cnt

	return s
}

One of the potential problems with the above implementation is that it suffers from the same issue as the Mutex and Cond structures of Go's sync package, if the user makes a copy of the struct, then things won't work correctly. So, for example, correct operation depends on the user not passing the object by value. Here's an alternative implementation that gets around this potential pitfall:

package semaphore

import (
	"sync"
)

type semaphore struct {
	cnt  int
	cond *sync.Cond
}

func (s *semaphore) v() {
	s.cond.L.Lock()
	s.cnt++
	s.cond.Signal()
	s.cond.L.Unlock()
}

func (s *semaphore) p() {
	s.cond.L.Lock()

	for done := false; !done; {
		if s.cnt > 0 {
			s.cnt--
			done = true
		} else {
			s.cond.Wait()
		}
	}
	s.cond.L.Unlock()
}

func newSemaphore(cnt int) *semaphore {
	s := new(semaphore)

	s.cond = sync.NewCond(new(sync.Mutex))
	s.cnt = cnt

	return s
}

type Semaphore struct {
	sem *semaphore
}

func (s *Semaphore) P() {
	s.sem.p()
}

func (s *Semaphore) V() {
	s.sem.v()
}

func NewSemaphore(cnt int) *Semaphore {
	s := new(Semaphore)
	s.sem = newSemaphore(cnt)

	return s
}

Here's a third version that adds a non-blocking P that returns boolean to let caller know if it succeeded, renames P and V to the better-known Acquire and Release, and lastly panics if Release is attempted when there are no semaphores left to release.

package semaphore

import (
	"sync"
)

type semaphore struct {
	initialCnt int
	cnt  int
	cond *sync.Cond
}

func (s *semaphore) v() {
	s.cond.L.Lock()
	defer s.cond.L.Unlock()

	if s.cnt < s.initialCnt {
		s.cnt++
		s.cond.Signal()
	} else {
		panic("No semaphores to release")
	}
}

func (s *semaphore) p() {
	s.cond.L.Lock()
	defer s.cond.L.Unlock()
	
	for done := false; !done; {
		if s.cnt > 0 {
			s.cnt--
			done = true
		} else {
			s.cond.Wait()
		}
	}
}

func (s *semaphore) tryP() bool {
	s.cond.L.Lock()
	defer s.cond.L.Unlock()
	result := false
	
	if s.cnt > 0 {
		s.cnt--
		result = true
	}
	return result
}

func newSemaphore(cnt int) *semaphore {
	return &semaphore{initialCnt: cnt, cnt: cnt, cond: sync.NewCond(new(sync.Mutex))}
}

type Semaphore struct {
	sem *semaphore
}

func (s *Semaphore) TryAcquire() bool {
	return s.sem.tryP()
}

func (s *Semaphore) Acquire() {
	s.sem.p()
}

func (s *Semaphore) Release() {
	s.sem.v()
}

func New(cnt int) *Semaphore {
	s := new(Semaphore)
	s.sem = newSemaphore(cnt)

	return s
}