go / pool
I use a handle-based pool to manage resources
that need specific removal, unlike sync.Pool
which can evict items at any GC cycle.
The problem
sync.Pool is designed for temporary object reuse:
var bufPool = sync.Pool{
New: func() any { return new(bytes.Buffer) },
}
buf := bufPool.Get().(*bytes.Buffer)
defer bufPool.Put(buf)
But sync.Pool has limitations:
- Items can disappear at any garbage collection
- You can't remove a specific item
- You can't iterate over what's in the pool
- Requires type assertions
For connection pools or worker tracking, you need items to persist and be individually removable.
The pattern
package pool
type Pool[V any] struct {
s []itemAndIndex[V]
}
type itemAndIndex[V any] struct {
item V
index *int
}
type Handle[V any] struct {
idx *int
}
func (p *Pool[V]) Add(item V) Handle[V] {
idx := new(int)
*idx = len(p.s)
p.s = append(p.s, itemAndIndex[V]{item: item, index: idx})
return Handle[V]{idx: idx}
}
func (p *Pool[V]) Delete(h Handle[V]) bool {
idx := *h.idx
if idx < 0 {
return false
}
*h.idx = -1 // mark deleted
// Swap with last element for O(1) removal
last := len(p.s) - 1
if idx < last {
p.s[idx] = p.s[last]
*p.s[idx].index = idx
}
p.s = p.s[:last]
return true
}
func (p *Pool[V]) TakeRandom() (V, bool) {
if len(p.s) == 0 {
var zero V
return zero, false
}
pick := rand.IntN(len(p.s))
h := Handle[V]{idx: p.s[pick].index}
item := p.s[pick].item
p.Delete(h)
return item, true
}
Key points:
- Handle-based removal:
Addreturns a handle;Deleteuses it. - O(1) deletion: Swap-with-last avoids shifting elements.
- No GC eviction: Items stay until explicitly removed.
- Random selection: Useful for load balancing.
Usage
Track active connections:
var connections pool.Pool[*Conn]
func onConnect(conn *Conn) Handle[*Conn] {
return connections.Add(conn)
}
func onDisconnect(h Handle[*Conn]) {
connections.Delete(h)
}
Load balance across workers:
var workers pool.Pool[*Worker]
func dispatch(job Job) bool {
worker, ok := workers.TakeRandom()
if !ok {
return false // no workers available
}
worker.Send(job)
return true
}
When to use
- Connection pools with dynamic membership
- Worker pools where you track active workers
- Any collection where items are added then later removed by identity
- Load balancing by random selection
When not to use
- Temporary object reuse to reduce allocations (use
sync.Pool) - Key-value lookups (use a map)
- Concurrent access without external synchronization (this pool is not thread-safe; wrap with a mutex if needed)