首页 > 后端开发 > Golang > 正文

Go 并发编程中的死锁问题及解决方案:使用 Channel 实现数据汇总

碧海醫心
发布: 2025-10-03 16:54:17
原创
717人浏览过

go 并发编程中的死锁问题及解决方案:使用 channel 实现数据汇总

并发编程中,死锁是一个常见的问题。尤其在使用 Go 语言的 Goroutine 和 Channel 进行并发操作时,如果处理不当,很容易导致死锁。本文将通过一个具体的例子,深入分析死锁产生的原因,并提供两种有效的解决方案。

死锁产生的原因分析

以下面的代码为例,该程序将一个整数数组分成两部分,然后使用两个 Goroutine 分别计算它们的和,并将结果发送到同一个 Channel 中。最后,主 Goroutine 从 Channel 中接收结果并求和。

package main

import (
    "fmt"
)

// Add adds the numbers in a and sends the result on res.
func Add(a []int, res chan<- int) {
    sum := 0
    for i := range a {
        sum = sum + a[i]
    }
    res <- sum
}

func main() {
    a := []int{1, 2, 3, 4, 5, 6, 7}

    n := len(a)
    ch := make(chan int)
    go Add(a[:n/2], ch)
    go Add(a[n/2:], ch)

    sum := 0
    for s := range ch {
        sum = sum + s
    }
    //close(ch)

    fmt.Println(sum)
}
登录后复制

这段代码存在死锁问题。原因在于 for s := range ch 循环会一直尝试从 Channel ch 中接收数据,直到 Channel 关闭。然而,在上面的代码中,Channel ch 始终没有被关闭。这意味着主 Goroutine 会一直阻塞在 for...range 循环中,等待 Channel 中有新的数据,而 Goroutine Add 在发送完数据后就结束了,没有关闭 Channel 的操作。因此,程序会一直等待下去,导致死锁。

解决方案一:使用计数器控制循环次数

第一种解决方案是不使用 range 循环,而是使用一个计数器来控制循环次数。在每次从 Channel 接收数据后,计数器递增,当计数器达到预期的 Goroutine 数量时,循环结束。

package main

import (
    "fmt"
)

// Add adds the numbers in a and sends the result on res.
func Add(a []int, res chan<- int) {
    sum := 0
    for i := range a {
        sum = sum + a[i]
    }
    res <- sum
}

func main() {
    a := []int{1, 2, 3, 4, 5, 6, 7}

    n := len(a)
    ch := make(chan int)
    go Add(a[:n/2], ch)
    go Add(a[n/2:], ch)

    sum := 0

    // counts the number of messages sent on the channel
    count := 0

    // run the loop while the count is less than the total number of routines
    for count < 2 {
        s := <-ch
        sum = sum + s
        count++ // Increment the count after a routine sends its value
    }
    fmt.Println(sum)
}
登录后复制

在这个修改后的版本中,我们使用 count 变量来记录从 Channel 中接收到的数据的数量。for count < 2 循环会一直执行,直到 count 的值达到 2,也就是 Goroutine 的数量。这样,即使 Channel 没有被关闭,循环也会在接收到所有 Goroutine 发送的数据后结束,从而避免死锁。

ClipDrop
ClipDrop

Stability.AI出品的图片处理系列工具(背景移除、图片放大、打光)

ClipDrop 112
查看详情 ClipDrop

解决方案二:在 Goroutine 完成任务后关闭 Channel

第二种解决方案是在所有的 Goroutine 完成任务后,关闭 Channel。这意味着需要有一种机制来确定所有的 Goroutine 都已经完成了任务。一种常见的方法是使用 sync.WaitGroup。

package main

import (
    "fmt"
    "sync"
)

// Add adds the numbers in a and sends the result on res.
func Add(a []int, res chan<- int, wg *sync.WaitGroup) {
    defer wg.Done() // Decrement the counter when the goroutine completes
    sum := 0
    for i := range a {
        sum = sum + a[i]
    }
    res <- sum
}

func main() {
    a := []int{1, 2, 3, 4, 5, 6, 7}

    n := len(a)
    ch := make(chan int)
    var wg sync.WaitGroup

    wg.Add(2) // Increment the counter for the number of goroutines

    go Add(a[:n/2], ch, &wg)
    go Add(a[n/2:], ch, &wg)

    go func() {
        wg.Wait()    // Wait for all goroutines to complete
        close(ch) // Close the channel after all goroutines are done
    }()

    sum := 0
    for s := range ch {
        sum = sum + s
    }

    fmt.Println(sum)
}
登录后复制

在这个修改后的版本中,我们使用了 sync.WaitGroup 来等待所有的 Goroutine 完成任务。wg.Add(2) 用于设置需要等待的 Goroutine 的数量。在每个 Goroutine 的 Add 函数中,我们使用 defer wg.Done() 来在 Goroutine 结束时递减计数器。另外启动一个 Goroutine 来等待所有 Add 函数执行完毕,然后关闭 channel。这样,当所有的 Goroutine 都完成任务后,wg.Wait() 会返回,然后我们关闭 Channel ch。这样,for...range 循环就可以正常结束,避免死锁。

总结与注意事项

在 Go 并发编程中使用 Channel 时,需要特别注意死锁问题。以下是一些建议:

  • 明确 Channel 的生命周期: 确定 Channel 何时应该被关闭。
  • 避免在发送者未完成时关闭 Channel: 只有在确定没有更多数据发送到 Channel 时,才应该关闭它。
  • 使用 sync.WaitGroup: 当需要等待多个 Goroutine 完成任务时,可以使用 sync.WaitGroup 来协调。
  • 选择合适的循环方式: 根据实际情况选择使用 for...range 循环或计数器控制的循环。

通过理解死锁产生的原因,并采用合适的解决方案,可以编写出更加健壮和可靠的并发程序。

以上就是Go 并发编程中的死锁问题及解决方案:使用 Channel 实现数据汇总的详细内容,更多请关注php中文网其它相关文章!

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

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

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

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