channel内存泄露

对于一个channel来说,如果没有任何goroutine引用,gc会对其进行回收,不会引起内存泄露。

而当goroutine处于接收或发送阻塞状态,channel处于空或满状态时,一直得不到改变,gc则无法回收这类一直处于等待队列中的goroutine,引起内存泄露。

引起内存泄露的几种情况:

  1. 发送端channel满了,没有接收端
  2. 接收端channel为空,没有发送端
  3. channel未初始化

情形1:

channel缓存队列已满,接收者goroutine退出,发送者阻塞且所在goroutine无法退出

func main(){
	channel := make(chan int, 5)
	//sender
	go func() {
		for i:=0; i<10; i++{
			channel <- i	//阻塞
			time.Sleep(10 * time.Millisecond)
			fmt.Println("sender count:", i)
		}
		fmt.Println("sender exit.")
	}()

	//receiver
	go func() {
		i := <- channel	//接收一个退出
		fmt.Println("receive:", i)
		fmt.Println("receiver exit.")
	}()

	for{
		time.Sleep(10 * time.Millisecond)
	}
}

情形2:

channel缓存队列为空,发送者goroutine退出,接收者阻塞且所在goroutine无法退出

func main(){
	channel := make(chan int, 5)
	//sender
	go func() {
		channel <- 1	//发一个退出
		fmt.Println("sender exit.")
	}()

	//receiver
	go func() {
		for i:=0; i<10; i++ {
			i := <-channel	//阻塞
			fmt.Println("receive:", i)
		}
		fmt.Println("receiver exit.")
	}()

	for{
		time.Sleep(10 * time.Millisecond)
	}
}

解决:

加一个通知另一段退出的stopChan

func main(){
	channel := make(chan int, 5)
	stopChan := make(chan struct{})//无缓冲的channel作为信号量

  //sender
  go func() {
    for i:=0; i<10; i++{
      select {
        case <-stopChan:	//收到close信号,退出。多个sender时,每个goroutine也都能收到
        fmt.Println("sender exit.")
        return
        case channel <- i:
        time.Sleep(10 * time.Millisecond)
        fmt.Println("sender count:", i)
      }
    }
    fmt.Println("sender exit.")
  }()

	//receiver
	go func() {
		i := <- channel
		close(stopChan)
		fmt.Println("receive:", i)
		fmt.Println("receiver exit.")
	}()

	for{
		time.Sleep(10 * time.Millisecond)
	}
}

备注:

从一个关闭的channel仍能读出数据,当缓冲队列还有数据时,会返回正常值;缓冲队列为空时,则返回channel已关闭。

向一个关闭的channel发送数据则会报Panic

参考:

https://juejin.cn/post/7033711399041761311

https://www.cnblogs.com/ricklz/p/11262069.html

### Goroutine 内存泄露原因 Goroutine 内存泄露的主要原因之一是没有正确管理其生命周期。如果启动的 Goroutine 缺乏明确的退出条件,则会持续运行并占用系统资源,从而引发内存泄露问题[^2]。具体来说: - **未正确关闭 Goroutine**:某些情况下,开发者可能忘记为 Goroutine 提供终止信号或逻辑,导致它永远处于活动状态。 - **依赖外部资源**:当 Goroutine 需要等待某个外部事件完成(如网络请求),而该事件未能触发时,可能会使 Goroutine 处于阻塞状态,无法释放所占有的内存资源[^2]。 - **无限循环**:在设计当的情况下,Goroutine 中可能存在无出口的循环结构,这同样会造成内存消耗断累积。 另外一种常见情形涉及 Channel 的使用。如果一个作为生产者的 Goroutine 向 Channel 发送数据,却没有相应的消费者来接收这些消息,那么这个 Channel 将会被填满,进而阻止进一步的数据写入操作,并连带引起整个 Goroutine 及其所关联对象的可回收现象[^3]。 ### 解决方案 针对以上提到的各种 Goroutine 内存泄露成因,可以采取如下措施加以预防和解决: #### 使用 Context 包管理 Goroutine 生命周期 通过 Go 标准库中的 `context` 包能够有效地控制多个 Goroutines 的生命期。Context 对象允许父级组件传递取消信号给子级 Goroutines ,使得后者能够在接收到通知之后优雅地停止执行并清理自己使用的资源[^2]。 ```go ctx, cancel := context.WithCancel(context.Background()) go func(ctx context.Context){ for { select { case <- ctx.Done(): fmt.Println("goroutine stopped") return default: // do work here... } } }(ctx) // When you want to stop the goroutine. cancel() ``` #### 确保 Channels 正常通信 为了避免由于 Channels 导致的 Goroutine 泄露问题,在创建 Producer 和 Consumer 类型的 Goroutines 时候应该特别注意两者之间的协调关系。确保每一个发送动作都有对应的接受处理过程;同时也要考虑到异常状况下的恢复机制,比如设置缓冲区大小以及超时限制等策略。 #### 利用 Slice 截断技术减少必要的引用保留 对于数组切片的操作也可能间接带来潜在的风险——即因为旧版本变量仍然保持着对原始底层数组片段的有效性而导致额外空间浪费的现象。对此可以通过重新定义新边界的方式来切断这种联系,仅保留实际需要用到的部分副本即可[^4]。 ```go var s0 = []int{1, 2, 3, 4, 5, 6, 7, 8, 9} s1 := append(s0[:0:0], s0[:5]...) ``` 此方法利用了零长度头部视图特性 (`s0[:0:0]`) 来构建新的独立存储区域,有效防止原生连续区块内的其他位置受到影响。 ---
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值