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.
This was originally posted at: https://www.tegh.net/9
Top comments (0)