惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Security Archives - TechRepublic
Security Archives - TechRepublic
博客园 - Franky
U
Unit 42
有赞技术团队
有赞技术团队
P
Proofpoint News Feed
H
Hackread – Cybersecurity News, Data Breaches, AI and More
量子位
博客园 - 【当耐特】
罗磊的独立博客
B
Blog
Stack Overflow Blog
Stack Overflow Blog
F
Fortinet All Blogs
爱范儿
爱范儿
The Cloudflare Blog
Engineering at Meta
Engineering at Meta
Hugging Face - Blog
Hugging Face - Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
腾讯CDC
月光博客
月光博客
酷 壳 – CoolShell
酷 壳 – CoolShell
A
About on SuperTechFans
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
V
V2EX
Microsoft Azure Blog
Microsoft Azure Blog
J
Java Code Geeks
Apple Machine Learning Research
Apple Machine Learning Research
N
Netflix TechBlog - Medium
Google DeepMind News
Google DeepMind News
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
MyScale Blog
MyScale Blog
阮一峰的网络日志
阮一峰的网络日志
H
Help Net Security
S
SegmentFault 最新的问题
aimingoo的专栏
aimingoo的专栏
B
Blog RSS Feed
L
LangChain Blog
T
Tailwind CSS Blog
WordPress大学
WordPress大学
GbyAI
GbyAI
Blog — PlanetScale
Blog — PlanetScale
V
Visual Studio Blog
Vercel News
Vercel News
Recent Announcements
Recent Announcements
博客园 - 司徒正美
S
Securelist
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
C
Cybersecurity and Infrastructure Security Agency CISA
L
LINUX DO - 热门话题
Security Latest
Security Latest
T
Threat Research - Cisco Blogs

博客园 - 若-飞

企业AI Agent落地的核心逻辑与路径 基于langchain,Function Call的成功率怎么解决? LangChain Checkpoint(检查点)是什么?—— Agent 的"存档机制" RAG 设计:Embedding 如何切分 AI 客服系统设计:RAG 知识库设计 Go 百万连接服务器设计:从网卡到业务的全链路解析 深度解析 sync.Pool:从设计哲学到生产实践 Go Channel 关闭与超时机制完全指南 Go Map 无限增长问题解决方案 LangChain 聊天记录压缩:原理、机制与实战 揭开 sklearn 文本分类的核心原理:从词袋到逻辑回归 大模型“胡说八道”怎么办?一张图读懂检测、评估与修复全方案 企业级AI知识库权限隔离设计:让AI“懂规矩”比“懂知识”更重要 RAG系统设计全解析:从架构到多模态的核心知识图谱 RAG召回率提升全攻略:7大核心方法让检索更精准 RAG召回率提升秘籍:Metadata过滤的底层原理与实践 构建更好的RAG系统:深入理解混合搜索 一文搞懂 RAG 中 Retriever 和 Reranker 的区别 一文搞懂 RAG 的召回率(Recall)是什么? LangChain / LangGraph、MCP、Harness Engineer 与 Claude Code 的对应关系 Agent Harness 技术笔记:从 Trajectory 到 Function Calling Loop BLEU 是什么?——从原理到工程实践 一文讲清:Approve / Permit / Permit2 的本质区别 分库分表后跨分页查询的完整方案 ai如何处理私有数据 ai幻觉是啥,以及如何解决 别再让大模型“凭空瞎猜”了!带你认识AI最强外挂:ChromaDB 用 useQuery 管请求:TanStack React Query 入门小结 HD钱包--BIP44 TRON 四种 API 面怎么选:从节点协议到 JSON-RPC 再到 TronGrid 以太坊节点存储与共识机制全解析 BSC节点发现协议全解析:UDP发现、Bootnode引导与Gossip交易广播 以太坊节点发现背后的分布式哈希表(DHT)与 Kademlia 原理解析 Solidity中的bytes与string:深入理解这两种特殊的动态数组 智能合约自毁:当资产还在,合约死了 —— 深度解析 selfdestruct 导致的资产锁定风险 TDengine CLI (taos) 使用指南 —— Docker 本地开发实战 在 macOS 上用 DBeaver 连接 TDengine:踩坑总结与最终配置指南 Solidity Storage Slot 深度解析 Geth Snapshot Export/Import 深度解析: 不是备份工具,而是数据分析利器 基于BSC 公链的数据备份与 Snapshot 机制深度解析 Docker 共享内存完全指南:从原理到实践,避免常见的理解误区 Docker容器"僵尸状态"问题排查与自动重启方案 SSE协议深度解析:被低估的HTTP服务器推送标准 TDengine vs MySQL:时序数据处理的时代之选 Proxmox 启用 QEMU Guest Agent 实战指南 解决 Blockscout "batch too large" 错误的完整指南 一文讲清楚什么是基准测试(Benchmark) Rust中的宏(Macro):编译时的代码生成魔法 Docker优雅关闭的艺术:为什么stop_grace_period能防止数据丢失 为什么 Go 没有依赖注入和 Bean 机制?语言设计哲学对比
Goroutine 泄漏:原因、检测与防范
若-飞 · 2026-07-12 · via 博客园 - 若-飞

Goroutine 泄漏:原因、检测与防范

防止 Goroutine 泄漏的核心是:每个 goroutine 都应有明确的退出路径,使用 context 控制生命周期,用 select 加超时保护 channel 操作,用 WaitGroup 等待完成,并持续监控数量变化。


一、什么是 Goroutine 泄漏?

Goroutine 泄漏是指 goroutine 因永久阻塞而无法正常退出,一直占用内存和资源,最终导致程序 OOM 或性能下降。

常见泄漏场景

// ❌ 场景1:从空 channel 接收
ch := make(chan int)
data := <-ch  // 永远阻塞(没人发送)

// ❌ 场景2:向已满的 channel 发送
ch := make(chan int, 1)
ch <- 1
ch <- 2  // 永远阻塞(没消费者)

// ❌ 场景3:等待不会发生的关闭
for v := range ch {  // 永远等不到 close
    fmt.Println(v)
}

// ❌ 场景4:goroutine 死循环无退出
go func() {
    for {
        doWork()  // 永远运行,无法停止
    }
}()

二、核心解决方案

1. 使用 Context 控制生命周期(推荐)

func worker(ctx context.Context, ch <-chan int) {
    for {
        select {
        case data, ok := <-ch:
            if !ok {
                return
            }
            process(data)
        case <-ctx.Done():
            fmt.Println("worker 退出:", ctx.Err())
            return
        }
    }
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    
    ch := make(chan int)
    go worker(ctx, ch)
    
    ch <- 1
    ch <- 2
    // 5秒后自动退出
}

2. 使用 WaitGroup 确保完成

func main() {
    var wg sync.WaitGroup
    ch := make(chan int, 10)
    
    // 生产者
    wg.Add(1)
    go func() {
        defer wg.Done()
        defer close(ch)
        for i := 0; i < 5; i++ {
            ch <- i
        }
    }()
    
    // 消费者
    wg.Add(1)
    go func() {
        defer wg.Done()
        for data := range ch {
            fmt.Println(data)
        }
    }()
    
    wg.Wait()  // 等待所有 goroutine 完成
    fmt.Println("所有 goroutine 已退出")
}

3. 所有 Channel 操作用 Select 保护

// ✅ 安全发送
func safeSend(ch chan<- int, data int, ctx context.Context) error {
    select {
    case ch <- data:
        return nil
    case <-ctx.Done():
        return ctx.Err()
    }
}

// ✅ 安全接收
func safeReceive(ch <-chan int, ctx context.Context) (int, error) {
    select {
    case data, ok := <-ch:
        if !ok {
            return 0, fmt.Errorf("channel 已关闭")
        }
        return data, nil
    case <-ctx.Done():
        return 0, ctx.Err()
    }
}

4. 监控 Goroutine 数量

func monitor() {
    ticker := time.NewTicker(10 * time.Second)
    defer ticker.Stop()
    
    for range ticker.C {
        count := runtime.NumGoroutine()
        fmt.Printf("当前 goroutine 数量: %d\n", count)
        
        if count > 100 {
            log.Printf("警告: goroutine 数量过多 (%d)", count)
            // 发送告警
        }
    }
}

三、完整示例:安全的 Worker Pool

package main

import (
    "context"
    "fmt"
    "sync"
    "time"
)

type Job struct {
    ID int
}

type Pool struct {
    jobCh    chan Job
    ctx      context.Context
    cancel   context.CancelFunc
    wg       sync.WaitGroup
    closeOnce sync.Once
}

func NewPool(workers, queueSize int) *Pool {
    ctx, cancel := context.WithCancel(context.Background())
    p := &Pool{
        jobCh:  make(chan Job, queueSize),
        ctx:    ctx,
        cancel: cancel,
    }
    
    for i := 0; i < workers; i++ {
        p.wg.Add(1)
        go p.worker(i)
    }
    return p
}

func (p *Pool) worker(id int) {
    defer p.wg.Done()
    
    for {
        select {
        case job, ok := <-p.jobCh:
            if !ok {
                return
            }
            fmt.Printf("Worker %d 处理 Job %d\n", id, job.ID)
            time.Sleep(100 * time.Millisecond)
            
        case <-p.ctx.Done():
            fmt.Printf("Worker %d 退出\n", id)
            return
        }
    }
}

func (p *Pool) Submit(job Job) bool {
    select {
    case p.jobCh <- job:
        return true
    case <-p.ctx.Done():
        return false
    default:
        fmt.Println("队列满,丢弃 Job:", job.ID)
        return false
    }
}

func (p *Pool) Shutdown(timeout time.Duration) {
    p.closeOnce.Do(func() {
        close(p.jobCh)
        
        done := make(chan struct{})
        go func() {
            p.wg.Wait()
            close(done)
        }()
        
        select {
        case <-done:
            fmt.Println("正常关闭")
        case <-time.After(timeout):
            fmt.Println("超时,强制关闭")
            p.cancel()
        }
    })
}

func main() {
    pool := NewPool(3, 5)
    
    // 提交任务
    for i := 0; i < 20; i++ {
        pool.Submit(Job{ID: i})
    }
    
    // 优雅关闭
    pool.Shutdown(2 * time.Second)
    fmt.Println("程序结束")
}

四、快速自查清单

□ 每个 goroutine 都有退出路径吗?
□ 使用了 context 控制生命周期吗?
□ channel 操作用 select 包裹了吗?
□ 有超时保护吗(time.After)?
□ 使用 WaitGroup 等待完成吗?
□ 监控 goroutine 数量变化吗?
□ 确保谁创建谁关闭 channel 吗?
□ 使用 sync.Once 防止重复关闭吗?

五、总结

方案 用途 关键代码
Context 生命周期控制 ctx.Done()
Select 非阻塞/超时操作 select { case ... }
WaitGroup 等待完成 wg.Add/Done/Wait
超时 防止永久阻塞 time.After
监控 提前发现问题 runtime.NumGoroutine()

核心原则

每个 goroutine 都必须知道"何时退出",而不是"是否会退出"。 使用 context、select、WaitGroup 组合,让退出路径清晰可控,并持续监控数量变化。