Skip to content

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

License

Notifications You must be signed in to change notification settings

dms00/go-semaphore

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 

Repository files navigation

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
}

About

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

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published