
本文旨在帮助开发者理解和解决 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 mainimport ( "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: %vn", 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 函数中
错误的做法:
不要试图通过增加 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: %vn", 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 mainimport ( "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: %vn", 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 缓冲区来掩盖问题。
以上就是Go 并发编程:解决 Goroutine Deadlock 问题的详细内容,更多请关注创想鸟其它相关文章!
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1397830.html
微信扫一扫
支付宝扫一扫