0

0

Go 并发编程:解决 Goroutine Deadlock 问题

DDD

DDD

发布时间:2025-08-15 20:38:01

|

346人浏览过

|

来源于php中文网

原创

go 并发编程:解决 goroutine deadlock 问题

本文旨在帮助开发者理解和解决 Go 语言中常见的 Goroutine Deadlock 问题。通过分析一个简单的 Observer Pattern 实现案例,我们将深入探讨 Deadlock 产生的原因,并提供两种有效的解决方案:使用带缓冲的 channel 或利用 sync.WaitGroup 进行 Goroutine 同步。避免简单地通过增加 channel 缓冲区来掩盖问题,强调理解并发机制的重要性。

在 Go 语言中,Goroutine 是轻量级的并发执行单元,而 channel 则是 Goroutine 之间通信和同步的重要机制。然而,不恰当的使用 channel 可能会导致 Deadlock,即所有 Goroutine 都处于阻塞状态,程序无法继续执行。下面我们通过一个 Observer Pattern 的例子来分析 Deadlock 的产生以及如何解决。

Deadlock 示例分析

考虑以下 Observer Pattern 的实现:

package main

import (
    "fmt"
)

type Publisher struct {
    listeners []chan int
}

type Subscriber struct {
    Channel chan int
    Name   string
}

func (p *Publisher) Sub(c chan int) {
    p.listeners = append(p.listeners, c)
}

func (p *Publisher) Pub(m int, quit chan int) {
    for _, c := range p.listeners {
        c <- m
    }
    quit <- 0
}

func (s *Subscriber) ListenOnChannel() {
    data := <-s.Channel
    fmt.Printf("Name: %v; Data: %v\n", s.Name, data)
}

func main() {
    quit := make(chan int)
    p := &Publisher{}
    subscribers := []*Subscriber{
        {Channel: make(chan int), Name: "1"},
        {Channel: make(chan int), Name: "2"},
        {Channel: make(chan int), Name: "3"},
    }
    for _, v := range subscribers {
        p.Sub(v.Channel)
        go v.ListenOnChannel()
    }

    p.Pub(2, quit)

    <-quit
}

这段代码尝试实现一个发布者-订阅者模式。Publisher 维护一个监听者列表,当发布消息时,将消息发送到每个监听者的 channel 中。Subscriber 监听自己的 channel,接收到消息后打印。

运行这段代码会抛出 "fatal error: all goroutines are asleep - deadlock!" 错误。

原因分析:

问题在于 p.Pub(2, quit) 函数。它在同一个 Goroutine 中同时向所有 listener 的 channel 发送数据,并向 quit channel 发送数据。由于 quit channel 是无缓冲的,这意味着 p.Pub 必须等待有人从 quit channel 接收数据才能继续执行。然而,main 函数中

错误的做法:

Kuwebs企业网站管理系统3.1.5 UTF8
Kuwebs企业网站管理系统3.1.5 UTF8

酷纬企业网站管理系统Kuwebs是酷纬信息开发的为企业网站提供解决方案而开发的营销型网站系统。在线留言模块、常见问题模块、友情链接模块。前台采用DIV+CSS,遵循SEO标准。 1.支持中文、英文两种版本,后台可以在不同的环境下编辑中英文。 3.程序和界面分离,提供通用的PHP标准语法字段供前台调用,可以为不同的页面设置不同的风格。 5.支持google地图生成、自定义标题、自定义关键词、自定义描

下载

不要试图通过增加 channel 的 buffer size 来解决这类问题。虽然增加 buffer size 可能会暂时缓解问题,但它只是掩盖了问题的本质,并没有真正解决并发逻辑上的错误。在开发初期,尽量使用无缓冲的 channel,这样可以更容易地发现并发问题。

解决方案

方案一:在 Subscriber 中发送 quit 信号

修改 ListenOnChannel 函数,在接收到数据后向 quit channel 发送信号。同时,在 main 函数中等待所有 Subscriber 完成。

func (s *Subscriber) ListenOnChannel(quit chan int) {
    data := <-s.Channel
    fmt.Printf("Name: %v; Data: %v\n", s.Name, data)
    quit <- 0 // Subscriber 完成后发送 quit 信号
}

func main() {
    quit := make(chan int, 3) // 创建带缓冲的 quit channel
    p := &Publisher{}
    subscribers := []*Subscriber{
        {Channel: make(chan int), Name: "1"},
        {Channel: make(chan int), Name: "2"},
        {Channel: make(chan int), Name: "3"},
    }
    for _, v := range subscribers {
        p.Sub(v.Channel)
        go v.ListenOnChannel(quit) // 传递 quit channel
    }

    p.Pub(2, quit)

    for i := 0; i < len(subscribers); i++ {
        <-quit // 等待所有 Subscriber 完成
    }
}

在这个方案中,quit channel 的 buffer size 需要设置为 3,因为需要接收来自三个 Subscriber 的信号。

方案二:使用 sync.WaitGroup

sync.WaitGroup 提供了更优雅的方式来等待一组 Goroutine 完成。

package main

import (
    "fmt"
    "sync"
)

type Publisher struct {
    listeners []chan int
}

type Subscriber struct {
    Channel chan int
    Name   string
}

func (p *Publisher) Sub(c chan int) {
    p.listeners = append(p.listeners, c)
}

func (p *Publisher) Pub(m int) {
    for _, c := range p.listeners {
        c <- m
    }
    // 关闭所有 channel,通知 Subscriber 结束
    for _, c := range p.listeners {
        close(c)
    }
}

func (s *Subscriber) ListenOnChannel(wg *sync.WaitGroup) {
    defer wg.Done() // Goroutine 结束时调用 Done
    for data := range s.Channel { // 使用 range 接收数据,channel 关闭后循环结束
        fmt.Printf("Name: %v; Data: %v\n", s.Name, data)
    }
}

func main() {
    var wg sync.WaitGroup
    p := &Publisher{}
    subscribers := []*Subscriber{
        {Channel: make(chan int), Name: "1"},
        {Channel: make(chan int), Name: "2"},
        {Channel: make(chan int), Name: "3"},
    }
    for _, v := range subscribers {
        p.Sub(v.Channel)
        wg.Add(1)              // 启动一个 Goroutine 前调用 Add
        go v.ListenOnChannel(&wg) // 传递 WaitGroup
    }

    p.Pub(2)
    wg.Wait() // 等待所有 Goroutine 完成
}

在这个方案中,我们使用 sync.WaitGroup 来跟踪所有 Subscriber Goroutine 的状态。在每个 Subscriber Goroutine 启动前,调用 wg.Add(1) 增加计数器。在 Goroutine 结束时,调用 wg.Done() 减少计数器。main 函数调用 wg.Wait() 等待计数器归零,即所有 Goroutine 完成。

同时,我们使用 close(c) 关闭了所有的 channel,这样 Subscriber 就可以通过 range 循环接收数据,当 channel 关闭时,循环自动结束。

总结

Deadlock 是 Go 并发编程中常见的问题,理解 Deadlock 产生的原因,并选择合适的解决方案至关重要。通过本文的分析,我们学习了如何避免 Deadlock,并掌握了两种有效的解决方案:使用带缓冲的 channel 或利用 sync.WaitGroup 进行 Goroutine 同步。在实际开发中,应根据具体场景选择合适的方案,并避免简单地通过增加 channel 缓冲区来掩盖问题。

相关文章

编程速学教程(入门课程)
编程速学教程(入门课程)

编程怎么学习?编程怎么入门?编程在哪学?编程怎么学才快?不用担心,这里为大家提供了编程速学教程(入门课程),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
scripterror怎么解决
scripterror怎么解决

scripterror的解决办法有检查语法、文件路径、检查网络连接、浏览器兼容性、使用try-catch语句、使用开发者工具进行调试、更新浏览器和JavaScript库或寻求专业帮助等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

228

2023.10.18

500error怎么解决
500error怎么解决

500error的解决办法有检查服务器日志、检查代码、检查服务器配置、更新软件版本、重新启动服务、调试代码和寻求帮助等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

297

2023.10.25

Golang channel原理
Golang channel原理

本专题整合了Golang channel通信相关介绍,阅读专题下面的文章了解更多详细内容。

248

2025.11.14

golang channel相关教程
golang channel相关教程

本专题整合了golang处理channel相关教程,阅读专题下面的文章了解更多详细内容。

344

2025.11.17

java入门学习合集
java入门学习合集

本专题整合了java入门学习指南、初学者项目实战、入门到精通等等内容,阅读专题下面的文章了解更多详细学习方法。

2

2026.01.29

java配置环境变量教程合集
java配置环境变量教程合集

本专题整合了java配置环境变量设置、步骤、安装jdk、避免冲突等等相关内容,阅读专题下面的文章了解更多详细操作。

2

2026.01.29

java成品学习网站推荐大全
java成品学习网站推荐大全

本专题整合了java成品网站、在线成品网站源码、源码入口等等相关内容,阅读专题下面的文章了解更多详细推荐内容。

0

2026.01.29

Java字符串处理使用教程合集
Java字符串处理使用教程合集

本专题整合了Java字符串截取、处理、使用、实战等等教程内容,阅读专题下面的文章了解详细操作教程。

0

2026.01.29

Java空对象相关教程合集
Java空对象相关教程合集

本专题整合了Java空对象相关教程,阅读专题下面的文章了解更多详细内容。

3

2026.01.29

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
10分钟--Midjourney创作自己的漫画
10分钟--Midjourney创作自己的漫画

共1课时 | 0.1万人学习

Midjourney 关键词系列整合
Midjourney 关键词系列整合

共13课时 | 0.9万人学习

AI绘画教程
AI绘画教程

共2课时 | 0.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号