题目
设计一个并发安全的资源池(Pool)
信息
- 类型:问答
- 难度:⭐⭐
考点
并发控制,资源管理,接口设计,错误处理
快速回答
实现一个并发安全的资源池需要关注以下要点:
- 使用
sync.Mutex或sync.RWMutex保护共享状态 - 通过
context.Context实现超时控制 - 使用带缓冲的 channel 管理资源
- 处理资源创建失败和池关闭场景
- 提供
Get()和Put()接口并处理资源回收
问题背景
在高并发场景中,频繁创建/销毁资源(如数据库连接、网络连接)会导致性能瓶颈。资源池通过复用资源减少开销,但需保证并发安全性和资源有效性。
核心实现方案
type Pool struct {
resources chan Resource
factory func() (Resource, error)
mu sync.Mutex
closed bool
}
func NewPool(factory func() (Resource, error), size int) *Pool {
p := &Pool{
resources: make(chan Resource, size),
factory: factory,
}
// 预初始化资源
for i := 0; i < size; i++ {
res, _ := factory()
p.resources <- res
}
return p
}
func (p *Pool) Get(ctx context.Context) (Resource, error) {
select {
case res := <-p.resources:
return res, nil
case <-ctx.Done():
return nil, ctx.Err()
default:
// 无可用资源时创建新资源
return p.factory()
}
}
func (p *Pool) Put(res Resource) {
p.mu.Lock()
defer p.mu.Unlock()
if p.closed {
res.Close() // 池已关闭时释放资源
return
}
select {
case p.resources <- res: // 放回资源
default:
res.Close() // 池满时释放资源
}
}
func (p *Pool) Close() {
p.mu.Lock()
defer p.mu.Unlock()
if p.closed {
return
}
p.closed = true
close(p.resources)
for res := range p.resources {
res.Close()
}
}关键设计点
- 并发控制:
- 使用
sync.Mutex保护closed状态 - 通过带缓冲的 channel 实现资源队列(线程安全)
- 使用
- 资源管理:
Get()优先从 channel 获取资源,无资源时调用 factory 创建Put()检查池状态,池满或关闭时释放资源
- 超时控制:
Get()使用context.Context支持超时取消
- 关闭处理:
Close()方法安全释放所有资源并标记状态- 禁止关闭后继续操作
最佳实践
- 资源健康检查:在
Put()时检测资源有效性,丢弃无效资源 - 动态扩容:记录创建的资源数量,限制最大资源数
- 泄漏防护:为资源添加最后使用时间戳,定期清理闲置资源
常见错误
- 竞态条件:未对
closed状态加锁导致数据竞争 - 资源泄漏:
Get()创建新资源后未在Put()中正确回收 - 死锁风险:
factory函数阻塞且无超时控制 - 无效资源:未检查放回资源是否已关闭
扩展知识
- 标准库参考:
sync.Pool适用于临时对象池,但无状态管理 - 连接池优化:
database/sql的连接池实现(最大打开数/最大闲置数) - 高级模式:
- 资源借用统计(如 Prometheus 指标)
- 基于权重的资源分配
- 故障资源自动剔除