题目
实现并发安全的资源池管理
信息
- 类型:问答
- 难度:⭐⭐
考点
channel缓冲机制, select超时控制, 并发资源管理, 错误处理
快速回答
实现要点:
- 使用带缓冲的channel作为资源池容器
- 通过select实现超时控制防止死锁
- 使用sync.Once确保资源关闭安全性
- 处理获取资源时的错误场景
问题场景
设计一个数据库连接池,要求:
- 支持并发获取/释放连接
- 获取连接时支持超时控制
- 连接耗尽时阻塞等待直到有资源释放
- 安全关闭资源池并处理残留请求
解决方案
type Pool struct {
resources chan *Resource
closed bool
closeOnce sync.Once
closeChan chan struct{}
}
func NewPool(size int) *Pool {
p := &Pool{
resources: make(chan *Resource, size),
closeChan: make(chan struct{}),
}
// 初始化资源
for i := 0; i < size; i++ {
p.resources <- NewResource()
}
return p
}
func (p *Pool) Acquire(timeout time.Duration) (*Resource, error) {
select {
case res := <-p.resources:
return res, nil
case <-time.After(timeout):
return nil, errors.New("timeout")
case <-p.closeChan:
return nil, errors.New("pool closed")
}
}
func (p *Pool) Release(res *Resource) {
select {
case p.resources <- res: // 正常归还
case <-p.closeChan: // 资源池已关闭
res.Close() // 直接销毁资源
}
}
func (p *Pool) Close() {
p.closeOnce.Do(func() {
close(p.closeChan) // 通知所有协程
close(p.resources) // 关闭channel
for res := range p.resources { // 清理残留资源
res.Close()
}
})
}核心原理
- 缓冲channel:固定容量的channel模拟资源池,长度即最大连接数
- select多路复用:同时监听资源获取、超时和关闭信号
- sync.Once:确保关闭操作只执行一次,避免panic
- 双通道关闭:closeChan通知释放协程立即终止,resources通道关闭触发资源清理
最佳实践
- 使用
make(chan T, size)创建缓冲通道作为容器 - 在Acquire中使用
time.After实现超时控制 - Release时检查关闭状态,避免向已关闭channel发送数据
- Close方法中先关闭通知通道,再处理资源通道
常见错误
| 错误示例 | 后果 | 修复方案 |
|---|---|---|
| 未处理超时 | 高并发时永久阻塞 | select+time.After |
| 重复关闭channel | 运行时panic | sync.Once保护 |
| 关闭后归还资源 | send on closed channel | Release中检查closeChan |
扩展知识
- 泄漏风险:goroutine阻塞在channel操作时,需通过context实现级联取消
- 动态扩容:结合sync.Mutex和条件变量实现弹性资源池
- 健康检查:在Release时验证资源有效性,销毁不可用资源
- atomic优化:使用atomic.Bool替代closed bool标志避免数据竞争