My previous post was about one of the concurrency patterns that can be achieved with go. I immediately got some feedback on it, and this is where I will address one of them.
You have seen the code reimplementing sync.WaitGroup
to have a semaphore in it.
type WaitGroup interface {
Add(delta int)
Done()
Wait()
}
type SemaphoredWaitGroup struct {
sem chan bool
wg sync.WaitGroup
}
func (s *SemaphoredWaitGroup) Add(delta int) {
s.wg.Add(delta)
s.sem <- true
}
func (s *SemaphoredWaitGroup) Done() {
<-s.sem
s.wg.Done()
}
func (s *SemaphoredWaitGroup) Wait() {
s.wg.Wait()
}
It has been brought to my attention (Thanks, Wojtek !), that there is a “cleaner” way to do it in Go.
Enter: Type Embedding
Those coming from other languages (e.g.: PHP), you might notice that there is no such thing as an extends
keyword.
There is no subclassing in the popular way. What we do have is type embedding
.
To achieve that we change SemaphoredWaitGroup
:
type SemaphoredWaitGroup struct {
sem chan bool
- wg sync.WaitGroup
+ sync.WaitGroup
}
All methods from the “inner” (embedded) struct are now a part of the “outer” (embedding) type. It is even possible to access them directly by calling them as if they were actually defined locally. E.g:
func (s *SemaphoredWaitGroup) newMethod() {
s.Wait()
}
There is a catch though. when we re-define the methods from the inner part (here we have: Add(delta int)
and Done()
), we need to change the internal calls
func (s *SemaphoredWaitGroup) Add(delta int) {
- s.Add(delta)
+ s.WaitGroup.Add(delta)
s.sem <- true
}
func (s *SemaphoredWaitGroup) Done() {
<-s.sem
- s.Done()
+ s.WaitGroup.Done()
}
because the s.Add(delta)
and s.Done()
would be recursive calls, and would result in a “stack overflow
” error in this particular instance.
Also, we don’t need to have the Wait()
method pass calls through to the underlying sync.WaitGroup
struct.
A complete example is available on Go Playground .