Go Channel 与 Goroutine 中指针变量引发的死锁问题解析


本文详解 go 中因未关闭 channel 和错误使用 sync.waitgroup 导致“all goroutines are asleep - deadlock”错误的根本原因,并提供可复用的并发控制模板。

在 Go 并发编程中,all goroutines are asleep - deadlock 是一个典型且易被忽视的运行时错误。它并非语法或逻辑错误,而是程序陷入永久阻塞状态:所有 goroutine 都在等待(如从 channel 读取、写入或等待 WaitGroup),却无人唤醒或终止,导致主 goroutine 无法继续执行而崩溃。

在你提供的代码中,问题根源有两点:

  1. sync.WaitGroup 计数不匹配:wg.Add(len(urls)) 错误地为每个 URL 增加计数,但实际只启动了 1 个 Poller goroutine(而非 len(urls) 个)。WaitGroup 的 Add() 应与 Done() 调用次数严格对应——即每个 goroutine 生命周期 对应一次 Add(1) + Done()。当前代码中,Poller 函数内仅调用一次 wg.Done(),但 main 中却 Add(len(urls)),造成计数严重失衡,wg.Wait() 永远不会返回。

  2. channel 未关闭导致无限阻塞:Poller 使用 for r := range in 循环持续接收数据。该循环仅在 channel 被显式关闭后才会退出。而你的写入 goroutine 在发送完所有 URL 后直接结束,既未关闭 pending channel,也未调用 wg.Done() 标记自身完成,导致 Poller 永远卡在 range 等待下一条数据,主 goroutine 则在 wg.Wait() 处死锁。

✅ 正确做法是:

  • wg.Add(n) 中的 n 表示需等待的 goroutine 数量(此处为 2:1 个 Poller + 1 个发送器);
  • 发送器 goroutine 完成后,先关闭 channel,再调用 wg.Done()(顺序很重要:关闭必须在所有发送操作之后,且 Done() 标识自身退出);
  • Poller 中使用 defer wg.Done() 确保 goroutine 结束时正确减计数。

以下是修复后的完整可运行示例(已适配 numPollers = 2 的并发模型):

package main

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

const numPollers = 2 // 启动 2 个并发 Poller

var urls = []string{
    "http://www.google.com/",
    "http://golang.org/",
    "http://blog.golang.org/",
    "http://golangtutorials.blogspot.fr",
    "https://gobyexample.com/",
}

type Resource struct {
    url string
}

// Poller 从 channel 拉取 *Resource 并处理(此处仅打印)
func Poller(in <-chan *Resource, wg *sync.WaitGroup) {
    defer wg.Done()
    for r := range in { // range 会自动在 channel 关闭后退出
        fmt.Printf("Processed: %s - %s\n", r.url, time.Now().Format("15:04:05"))
    }
}

func main() {
    var wg sync.WaitGroup
    pending := make(chan *Resource, len(urls)) // 可选:加缓冲避免发送阻塞

    // 启动 numPollers 个 Poller goroutine
    wg.Add(numPollers)
    for i := 0; i < numPollers; i++ {
        go Poller(pending, &wg)
    }

    // 启动 1 个发送 goroutine:写入 URL 并关闭 channel
    wg.Add(1)
    go func() {
        defer close(pending) // ✅ 关键:发送完成后关闭 channel
        defer wg.Done()      // ✅ 标记发送器 goroutine 完成
        for _, url := range urls {
            fmt.Printf("Sending: %s\n", url)
            pending <- &Resource{url: url}
        }
    }()

    wg.Wait() // 等待所有 Poller 和发送器完成
    fmt.Printf("✅ All done at %s\n", time.Now().Format("15:04:05"))
}

? 关键注意事项

  • close(pending) 必须由唯一写入者(即发送 goroutine)调用,且只能调用一次;多个 goroutine 写入时需额外协调。
  • 若需限制并发请求数(如 HTTP 调用),应在 Poller 内部实现(例如用 http.Client 发起请求),而非依赖 channel 缓冲区大小。
  • 使用 make(chan *Resource, N) 设置缓冲区可避免发送端阻塞,但不解决死锁本质——channel 关闭仍是 range 退出的必要条件
  • defer 语句按后进先出(LIFO)执行,因此 close(pending) 会在 wg.Done() 之前执行,确保 Poller 能收到关闭信号。

掌握 channel 生命周期与 WaitGroup 计数的精确匹配,是写出健壮 Go 并发程序的基石。


# go  # golang  # ai  # google  # 并发编程  # 并发请求  # Resource  # for  # 循环  # 指针  # len  # 并发  # channel  # http  # 死锁  # 而非  # 是一个  # 完成后  # 都在  # 多个  # 才会  # 会在  # 仍是  # 很重要 


相关栏目: 【 Google疑问12 】 【 Facebook疑问10 】 【 网络优化76771 】 【 技术知识130152 】 【 IDC云计算60162 】 【 营销推广131313 】 【 AI优化88182 】 【 百度推广37138 】 【 网站推荐60173 】 【 精选阅读31334


相关推荐: Win10如何设置双wan路由器 Win10双wan路由器设置方法【指南】  Windows如何设置登录时的欢迎屏幕背景?(锁屏界面)  Win11怎么设置虚拟内存最佳大小_Windows11性能选项自定义分页文件  Mac的访达(Finder)怎么用_Mac文件管理入门教程【详解】  Windows 11登录时提示“用户配置文件服务登录失败”怎么办_Windows 11修复损坏的用户配置文件  Python数据挖掘核心算法实践_聚类分类与特征工程  Win11怎么设置任务栏图标大小_Windows11注册表TaskbarSi修改  Mac如何调整Dock栏大小和位置_Mac程序坞个性化设置  小程序里php怎么变mp4_小程序调用php生成mp4视频方法【教程】  Windows驱动无法加载错误解决方法_驱动签名验证失败处理步骤  Windows10电脑怎么设置虚拟内存_Win10高级系统设置性能  Avalonia如何实现跨窗口通信 Avalonia窗口间数据传递  c++怎么设置线程优先级与cpu亲和性_c++ 多核处理器性能绑定【指南】  PHP接收参数值为空怎么办_判断和处理空参数方法说明【说明】  c# 在高并发场景下,委托和接口调用的性能对比  如何在 Go 中判断变量是否为函数类型  Windows 11怎么更改锁屏超时时间_Windows 11电源选项中设置屏幕关闭时间  php485在macos下怎么配置_php485 macOS系统配置指南【解答】  Win11麦克风没声音怎么设置_Win11麦克风权限及驱动修复【教程】  c++如何打印函数堆栈信息_c++ backtrace函数与符号名解析【方法】  Win10如何优化内存使用_Win10内存优化技巧【攻略】  如何在 Go 中调用动态链接库(.so)中的函数  PHP的FastAdmin架构适合二次开发吗_特点分析【介绍】  phpstudy本地环境mysql忘记密码_重置mysqlroot密码操作流程【解答】  Linux怎么修改用户密码_Linux系统passwd命令使用与权限管理【方法】  php怎么连接数据库_MySQL数据库连接的基础代码编写【说明】  如何在Golang中指定模块版本_使用go.mod控制版本号  Python函数接口文档化_自动化说明【指导】  Win10电脑怎么设置休眠快捷键_Windows10电源按钮功能定义  Win10文件历史记录怎么用 Win10开启自动备份文件教程【防丢】  Windows服务持续崩溃怎样修复_系统服务保护机制解析  Mac如何彻底清理浏览器缓存?(Safari与Chrome)  c++怎么处理多线程死锁_c++ lock_guard与unique_lock锁管理【技巧】  C++中的constexpr和const有什么区别?(编译期常量)  Python并发安全问题_资源竞争说明【指导】  Win11开机速度慢怎么优化_Win11系统启动加速设置指南【方法】  Win11怎么更改文件夹图标_自定义Win11文件夹外观样式【详解】  Win11资源管理器卡顿怎么办 Win11文件资源管理器重启技巧【优化】  Windows 11无法安全删除U盘提示设备正在使用中怎么办_Windows 11找出占用设备进程  Win11怎么设置快速访问主页_Windows11资源管理器文件夹选项  Win10如何更改电脑休眠时间_Windows10电源和睡眠选项调整  php高频调试功能有哪些_php常用调试函数与工具汇总【解答】  Win11如何设置开机问候语 Win11修改登录界面提示【技巧】  如何使用正则表达式精确匹配最多含一个换行符的 start-end 区段  Win11怎么恢复误删照片_Win11数据恢复工具使用【推荐】  如何用正则表达式精确匹配“start”到“end”之间最多含一个换行符的文本段  Win11怎么设置DNS服务器_Windows11修改网络适配器DNS优选  如何高效获取循环末次生成的 NumPy 数组最后一个元素(无需额外循环)  Win10怎样卸载自带Edge_Win10卸载Edge浏览器步骤【教程】  Win11怎么卸载Photos应用_Win11卸载Photos应用方法【教程】 

 2025-12-31

了解您产品搜索量及市场趋势,制定营销计划

同行竞争及网站分析保障您的广告效果

点击免费数据支持

提交您的需求,1小时内享受我们的专业解答。

致胜网络推广营销网


致胜网络推广营销网

致胜网络推广营销网专注海外推广十年,是谷歌推广.Facebook广告全球合作伙伴,我们精英化的技术团队为企业提供谷歌海外推广+外贸网站建设+网站维护运营+Google SEO优化+社交营销为您提供一站式海外营销服务。

 915688610

 17370845950

 915688610@qq.com

Notice

We and selected third parties use cookies or similar technologies for technical purposes and, with your consent, for other purposes as specified in the cookie policy.
You can consent to the use of such technologies by closing this notice, by interacting with any link or button outside of this notice or by continuing to browse otherwise.