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

Go语言防止goroutine重复执行 实现连接重连防抖动功能

实现方案

原方案存在的问题

  • err 通道如果持续涌入大量错误,没有做丢弃处理,极端情况会导致发送侧阻塞
  • isDialing 仅靠标识位控制,没有覆盖拨号本身的耗时区间,容易出现冷却结束后拨号还在进行时重复触发的问题
  • time.AfterFunc 启动的 goroutine 没有绑定生命周期,若函数提前退出会出现 goroutine 泄漏
  • 未处理 net.Dial 本身的失败情况,也没有处理向 w 通道写入时可能出现的阻塞场景

优化后实现代码

func keepConnUp(n netAddr, w chan<- io.Writer, err chan error) {
    addr := fmt.Sprintf("%s:%d", n.Addr, n.Port)
    // 冷却定时器,初始为nil不会触发
    var coolDownTimer *time.Timer
    // 标记是否正在执行拨号逻辑,原子变量避免并发竞争
    var isDialing atomic.Bool

    defer func() {
        if coolDownTimer != nil {
            coolDownTimer.Stop()
        }
    }()

    for {
        select {
        case <-err:
            // 已经在拨号/冷却中就直接丢弃当前错误信号,不处理
            if isDialing.Load() || (coolDownTimer != nil && time.Until(coolDownTimer.C) > 0) {
                continue
            }
            // 启动冷却计时
            coolDownTimer = time.NewTimer(reconnectTimer)
        case <-coolDownTimer.C:
            // 标记进入拨号状态
            isDialing.Store(true)
            // 执行拨号,增加超时避免长期卡住
            conn, err := net.DialTimeout(n.Network, addr, 5*time.Second)
            if err != nil {
                // 拨号失败自动触发下一次冷却重连
                coolDownTimer.Reset(reconnectTimer)
                isDialing.Store(false)
                continue
            }
            // 非阻塞写入w通道,避免接收侧阻塞导致本函数卡住
            select {
            case w <- conn:
            default:
                // 写入失败关闭多余连接避免资源泄漏,可自行补充日志逻辑
                _ = conn.Close()
            }
            isDialing.Store(false)
        }
    }
}

核心特性说明

  • 所有错误信号如果处于冷却期/拨号期直接丢弃,完全不会阻塞err通道的发送侧,适配大量错误涌入的场景
  • 用 atomic.Bool 替代普通布尔变量做状态标识,避免并发读写的竞争问题
  • 拨号逻辑增加超时控制,避免拨号卡住长期占用状态
  • 定时器统一管理,退出时自动停止避免资源泄漏
  • 拨号失败自动重置冷却定时器,不需要依赖额外错误信号触发下一次重试

内容的提问来源于stack exchange,提问作者Ado Ren

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 14:45:02