我目前正在阅读“Go in Action”,发现了有关 Go 中资源池实现的部分。这本书提供了一个完整的例子,但是有一个关于防止死锁的特定部分我觉得很困惑。下面是所描述的实现,包括关闭资源通道之前书中的评论:
package pool
import (
"errors"
"log"
"io"
"sync"
)
type Pool struct {
m sync.Mutex
resources chan io.Closer
factory func() (io.Closer, error)
closed bool
}
var ErrPoolClosed = errors.New("Pool has been closed")
func New(fn func() (io.Closer, error), size uint) (*Pool, error) {
if size <= 0 {
return nil, errors.New("Size value too small")
}
return &Pool{
factory: fn,
resources: make(chan io.Closer, size),
}, nil
}
func (p *Pool) Acquire() (io.Closer, error) {
select {
case r, ok := <-p.resources:
if !ok {
return nil, ErrPoolClosed
}
log.Println("Acquire:", "Shared Resource")
return r, nil
default:
log.Println("Acquire:", "New Resource")
return p.factory()
}
}
func (p *Pool) Release(r io.Closer) {
p.m.Lock()
defer p.m.Unlock()
if p.closed {
r.Close()
return
}
select {
case p.resources <- r:
log.Println("Release:", "In Queue")
default:
log.Println("Release:", "Closing")
r.Close()
}
}
func (p *Pool) Close() {
p.m.Lock()
defer p.m.Unlock()
if p.closed {
return
}
p.closed = true
// Close the channel before we drain the channel of its resources, if we don't do this, we will have a deadlock.
close(p.resources)
for r := range p.resources {
r.Close()
}
}
有问题的评论是:
// Close the channel before we drain the channel of its resources, if we don't do this, we will have a deadlock.
我对这个说法感到困惑。 Acquire 方法使用带有默认情况的 select 语句来获取资源,这意味着如果通道为空,它不应阻塞;它要么检索现有资源,要么创建新资源。鉴于这种非阻塞行为,所描述的方法如何防止死锁?我无法理解书上建议的如果通道在清空之前没有关闭就可能发生的死锁情况。
有人可以帮助解释或澄清这种潜在的僵局情况吗?我正在寻求对这个特定实现中发挥作用的并发机制的更深入理解。
我尝试过的:
为了理解“Go in Action”中提到的潜在死锁场景,我仔细查看了 Pool 结构体的实现细节,特别关注 Close 和 Acquire 方法。我还在受控环境中尝试了类似的模式,以观察通道在排空时关闭时和未关闭时的行为。我本来期望要么自己遇到死锁情况,要么从书中提供的代码中了解这种死锁的理论基础。
我所期待的:
根据书中的评论,我预计在耗尽通道之前不关闭通道会导致僵局。但是,考虑到 Acquire 方法的非阻塞设计(该方法使用具有默认情况的 select 语句从通道获取可用资源或创建新资源),尚不清楚如何发生死锁。我的期望是 Acquire 方法的设计本质上可以通过避免资源获取阻塞来防止死锁。因此,我一直在寻找死锁的场景,理论上,所有 goroutine 可能最终会互相等待来释放资源,但实现似乎阻止了这种情况。
@JimB 的评论澄清了“Go in Action”书籍示例中有关在耗尽通道之前关闭通道的潜在死锁问题。我错过了如果通道未关闭,通道上的 for range 循环就会挂起,从而导致书中提到的死锁场景。非常感谢@JimB 指出了这个关键细节!