I'm extending the sync.Pool
type by limiting the amount of objects that can reside outside the pool. To achieve this I'm using sync.Cond
to wait until a call to Pool.Put
is made. To prevent users from creating a zero value of my struct, I'm not exporting it and defined an interface too.
My question is: when checking the condition to Cond.Wait
the sync
package advises to Wait
in a loop. But considering my current implementation I think that using an if
suffices. Given the code below and assuming that nothing wil be modified, can I change the for
loop around Cond.Wait
to a simple if
?
type pool struct {
pool sync.Pool
n int
max int
lock sync.Mutex
cond *sync.Cond
}
type Pool interface {
Get() any
Put(x any)
}
func NewPool(n int, f func() any) Pool {
p := &pool{
pool: sync.Pool{
New: f,
},
max: n,
}
p.cond = sync.NewCond(&p.lock)
return p
}
func (p *pool) Get() any {
p.lock.Lock()
defer p.lock.Unlock()
// change to if?
for p.n >= p.max {
p.cond.Wait()
}
p.n++
return p.pool.Get()
}
func (p *pool) Put(x any) {
p.pool.Put(x)
p.lock.Lock()
defer p.lock.Unlock()
if p.n > 0 {
p.n--
}
if p.n < p.max {
p.cond.Signal()
}
}
Wait
just releastes the mutex lock, and suspends the routine whereWait
is called. WhenWait
returns, there's absolutely no way you can be certain thatp.n >= p.max
unless you check it again. You may think thatWait
will return becausePut
was called, butWait
can return because another routine calledGet
. \$\endgroup\$Wait
return whenGet
is called? \$\endgroup\$Signal
, using a conditional, though, would quickly become problematic if you switch over toSignal
. this example is all in all rather simplistic, and real-world code more often than not does merit yourWait
condition to be re-evaluated. The recommended pattern (and more defensive approach) therefore is to use a loop to ensure the condition is checked onceWait
re-acquires the lock. You could technically use anif
at the moment, but with the same number of lines, your code is, in many ways, better with thefor
\$\endgroup\$