You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Golang中实现Goroutine等待机制:避免资源池满时返回nil

解决Golang资源池满时goroutine等待可用Page实例的问题

当前资源池实现的核心问题是:当池满且无空闲Page时直接返回nil,不符合“无错误绝不返回nil”的需求。我们可以通过引入sync.Cond条件变量实现阻塞等待逻辑,直到有可用资源或上下文取消。

1. 修改Browse结构体,添加条件变量

条件变量需要与互斥锁配合使用,用于等待和唤醒goroutine:

type Browse struct {
    b       *rod.Browser
    ctx     context.Context
    pool    []*Page
    launch  *Launch
    maxSize int
    mu      sync.Mutex
    cond    *sync.Cond // 新增:用于等待/唤醒的条件变量
}

注意:在初始化Browse实例时,需要初始化条件变量:

// 示例初始化代码
func NewBrowse(b *rod.Browser, ctx context.Context, maxSize int) *Browse {
    browse := &Browse{
        b:       b,
        ctx:     ctx,
        maxSize: maxSize,
    }
    browse.cond = sync.NewCond(&browse.mu) // 绑定互斥锁
    return browse
}

2. 重写Page方法,实现阻塞等待逻辑

修改后的方法会循环检查资源,无可用资源时阻塞等待,直到被唤醒或上下文取消:

func (b *Browse) Page(proxy string, url string) (*Page, error) {
    b.mu.Lock()
    defer b.mu.Unlock()

    // 循环等待:处理虚假唤醒+重复检查资源状态
    for {
        // 优先复用空闲Page
        for i := range b.pool {
            if !b.pool[i].IsActive() {
                err := b.pool[i].SetUp(proxy)
                if err != nil {
                    return nil, err
                }
                return b.pool[i], nil
            }
        }

        // 池未满则创建新Page
        if len(b.pool) < b.maxSize {
            p := stealth.MustPage(b.b)

            page, err := NewPage(p, proxy, url)
            if err != nil {
                return nil, err
            }

            b.pool = append(b.pool, page)
            return page, nil
        }

        // 池满且无空闲:等待唤醒,同时监听上下文取消
        select {
        case <-b.ctx.Done():
            return nil, b.ctx.Err()
        default:
            b.cond.Wait() // 自动释放锁,被唤醒后重新获取锁
        }
    }
}

3. 修改PutPage方法,唤醒等待的goroutine

当Page被放回并清理后,需要通知等待队列中的goroutine有资源可用:

func (b *Browse) PutPage(page *Page) {
    b.mu.Lock()
    defer func() {
        b.mu.Unlock()
        b.cond.Signal() // 唤醒一个等待的goroutine(若用Broadcast则唤醒所有,按需选择)
    }()

    err := page.CleanUp()
    if err != nil {
        fmt.Println("Error cleaning up page", err)
    }
}

关键逻辑说明

  • 虚假唤醒处理:使用for循环而非if检查资源状态,避免Wait()被虚假唤醒后直接返回无效资源。
  • 上下文取消支持:通过select监听ctx.Done(),防止goroutine无限等待。
  • 效率优化:使用Signal()仅唤醒一个等待的goroutine,比Broadcast()更高效(若无需批量唤醒场景)。

使用方式不变

原有的调用代码无需修改,现在方法在无错误时将始终返回有效的*Page实例:

page, err := v.browse.Page(s.proxy, s.url)
if err != nil {
    fmt.Println("Error getting page", err)
    return
}
defer v.browse.PutPage(page)

内容的提问来源于stack exchange,提问作者nerthesa 12

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 00:44:49