Go WebSocket 协程泄漏问题分析与解决方案

问题背景:

在开发基于 WebSocket 的实时通信服务时,遇到了严重的协程泄漏问题。每次客户端连接都会创建新的协程,但这些协程无法正常退出,导致内存使用量不断增长,最终可能导致程序崩溃。

问题分析:

原始代码存在的问题

  1. Manager 协程重复启动
Go 复制代码
//  问题代码:每次连接都启动新的 Manager 协程

func Start(c *gin.Context) {

    Run(c.Writer, c.Request)

    go Manager.Start()  // 每次连接都启动新的协程!

}
  1. 每个连接启动多个协程
Go 复制代码
//  问题代码:每个连接启动读写协程

go ClientSocket.Read()   // 读协程

go ClientSocket.Write()  // 写协程
  1. 缺乏协程退出机制
Go 复制代码
// 问题代码:Manager 无限循环,无法退出

func (manager *ClientManager) Start() {

    for {  // 无限循环,没有退出条件

        select {

        case client := <-manager.Connect:

            manager.RegisterClient(client)

        // ...

        }

    }

}

解决方案:

1.使用统一 Manager 管理

修改前:

Go 复制代码
// 每次连接都启动 Manager

func Start(c *gin.Context) {

    Run(c.Writer, c.Request)

    go Manager.Start()  // ❌ 重复启动

}

修改后:

Go 复制代码
// main.go - 程序启动时只启动一次

func main() {

    // ... 其他初始化代码 ...

    go webSocket.Manager.Start()  // ✅ 只启动一次

    // ... 其他代码 ...

}

// server.go - 连接处理不再启动 Manager

func Start(c *gin.Context) {

    Run(c)

    // 移除 go Manager.Start()  // ✅ 不再重复启动

}

修改前:

Go 复制代码
//  每个连接启动两个协程

go ClientSocket.Read()   // 读协程

go ClientSocket.Write()  // 写协程

修改后:

Go 复制代码
// 改为同步处理,避免协程泄漏

ClientSocket.Read()  // 直接调用,不启动新协程

ClientSocket.setClosed("Run-main-exit")  // 连接结束时清理资源
  1. 添加连接状态管理

新增字段:

Go 复制代码
type ClientSession struct {

    // ... 原有字段 ...

    IsClosed      bool          // 连接是否已关闭

    CloseMutex    sync.Mutex    // 保护 IsClosed 字段的互斥锁

    WriteMutex    sync.Mutex    // 保护 WebSocket 写入操作的互斥锁

    CloseChan     chan struct{} // 连接关闭通知通道

}
  1. 实现协程退出机制
Go 复制代码
// 检查连接是否已关闭

func (client *ClientSession) isClosed() bool {

    client.CloseMutex.Lock()

    defer client.CloseMutex.Unlock()

    return client.IsClosed

}



// 设置连接为已关闭状态,统一处理资源清理

func (client *ClientSession) setClosed(caller string) {

    client.CloseMutex.Lock()

    defer client.CloseMutex.Unlock()

   

    if client.IsClosed {

        return

    }

   

    client.IsClosed = true

   

    // 通知 Manager 处理断开连接

    select {

    case Manager.DisConnect <- client:

    default:

        // 防止通道阻塞

    }

   

    // 关闭 WebSocket 连接

    if client.Conn != nil {

        _ = client.Conn.Close()

    }

   

    // 安全关闭通知通道

    select {

    case <-client.CloseChan:

    default:

        close(client.CloseChan)

    }

}
  1. 改进 Read 方法支持优雅退出
Go 复制代码
func (client *ClientSession) Read() {

    for {

        select {

        case <-client.CloseChan:  // ✅ 支持退出信号

            log.Printf("客户端 %s 的 Read 协程接收到连接关闭通知,正在退出", client.UserName)

            return

        default:

            // 处理消息...

            messageType, message, err := client.Conn.ReadMessage()

            if err != nil {

                return

            }

            // 处理消息逻辑...

        }

    }

}
  1. 改进消息发送机制
Go 复制代码
func SendMsg(client *ClientSession, commandStruct constants.CommandStruct) {

    // 首先检查连接是否已关闭

    if client.isClosed() {

        log.Printf("连接已关闭,无法发送消息给客户端: %s", client.UserName)

        return

    }

   

    // 获取写锁,保护并发写操作

    client.WriteMutex.Lock()

    defer client.WriteMutex.Unlock()

   

    // 安全发送消息

    if err := client.Conn.WriteMessage(websocket.TextMessage, msg); err != nil {

        log.Printf("向客户端 %s 发送消息失败: %v", client.UserName, err)

        client.setClosed("SendMsg-error")

    }

}

解决效果:

修改前的问题

  • ❌ 每次连接都创建新的 Manager 协程

  • ❌ 每个连接启动多个读写协程

  • ❌ 协程无法正常退出

  • ❌ 内存使用量不断增长

  • ❌ 系统性能下降

修改后的改进

  • ✅ Manager 只启动一次

  • ✅ 连接处理改为同步方式

  • ✅ 完善的协程退出机制

  • ✅ 内存使用稳定

  • ✅ 系统性能提升

总结

协程泄漏是 Go 并发编程中的常见问题,特别是在 WebSocket 服务中。通过合理的架构设计和资源管理,可以有效避免协程泄漏问题,提高系统的稳定性和性能。

希望这个经验分享对大家有所帮助!

相关推荐
学Java的bb3 小时前
MybatisPlus
java·开发语言·数据库
讓丄帝愛伱3 小时前
Mybatis Log Free插件使用
java·开发语言·mybatis
心之伊始3 小时前
Netty线程模型与Tomcat线程模型对比分析
java·开发语言
专注VB编程开发20年4 小时前
csdn手机app应该增加导入word格式markdown格式,或者输入网址自动导入
开发语言
ChineHe4 小时前
Golang并发编程篇001_并发编程相关概念解释
开发语言·后端·golang
@大迁世界4 小时前
Promise.all 与 Promise.allSettled:一次取数的小差别,救了我的接口
开发语言·前端·javascript·ecmascript
情深不寿3174 小时前
C++特殊类的设计
开发语言·c++·单例模式
流星白龙4 小时前
【Qt】3.认识 Qt Creator 界面
java·开发语言·qt