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

相关专题

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

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

184

2023.10.18

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

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

263

2023.10.25

Golang channel原理
Golang channel原理

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

239

2025.11.14

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

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

320

2025.11.17

php源码安装教程大全
php源码安装教程大全

本专题整合了php源码安装教程,阅读专题下面的文章了解更多详细内容。

7

2025.12.31

php网站源码教程大全
php网站源码教程大全

本专题整合了php网站源码相关教程,阅读专题下面的文章了解更多详细内容。

4

2025.12.31

视频文件格式
视频文件格式

本专题整合了视频文件格式相关内容,阅读专题下面的文章了解更多详细内容。

7

2025.12.31

不受国内限制的浏览器大全
不受国内限制的浏览器大全

想找真正自由、无限制的上网体验?本合集精选2025年最开放、隐私强、访问无阻的浏览器App,涵盖Tor、Brave、Via、X浏览器、Mullvad等高自由度工具。支持自定义搜索引擎、广告拦截、隐身模式及全球网站无障碍访问,部分更具备防追踪、去谷歌化、双内核切换等高级功能。无论日常浏览、隐私保护还是突破地域限制,总有一款适合你!

7

2025.12.31

出现404解决方法大全
出现404解决方法大全

本专题整合了404错误解决方法大全,阅读专题下面的文章了解更多详细内容。

42

2025.12.31

热门下载

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

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
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号