首页电脑使用go语言并发之道 pdf go语言 并发

go语言并发之道 pdf go语言 并发

圆圆2025-08-15 21:01:19次浏览条评论

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

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

在 Go 语言中,Goroutine 是轻量级的并发执行单元,而通道是 Goroutine 之间通信和同步的重要机制。然而,不适当的使用通道可能会导致死锁,即所有 Goroutine 都处于阻塞状态,程序无法继续执行。下面我们通过一个观察者模式的例子来分析死锁的产生以及解决。死锁代码分析

考虑以下 Observer Pattern 的实现:package mainimport ( quot;fmtquot;)type Publisher struct {listeners []chan int}type Subscriber struct { Channel chan int 名称 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 lt;- m } quit lt;- 0}func (s *Subscriber) ListenOnChannel() { data := lt;-s.Channel fmt.Printf(quot;名称: v;数据: v\nquot;, s.Name, data)}func main() { quit := make(chan int) p := amp;Publisher{} subscriptions := []*Subscriber{ {Channel: make(chan int), 名称: quot;1quot;}, {Channel: make(chan int), 名称: quot;2quot;}, {Channel: make(chan int), Name: quot;3quot;}, } for _, v := range订阅者 { p.Sub(v.Channel) go v.ListenOnChannel() } p.Pub(2, quit) lt;-quit}登录后复制

请代码尝试实现一个发布者-订阅者模式。维护一个监听者列表,当发布消息时,将消息发送到每个监听者的频道中。

订阅者监听自己的通道,接收到消息后打印。

运行代码会抛出“致命错误:所有 goroutine 都在睡觉 - 死锁!” 错误。

原因分析:

问题存在 p.Pub(2, quit) 函数。它在同一个 Goroutine 中同时向所有监听器的通道发送数据,从而退出通道发送数据。由于退出通道是无缓冲的,意味着 p.Pub 必须有人等待从退出通道接收数据才能执行。但是,main 函数继续中

错误的做法:

不要尝试通过通道增加通道的缓冲区大小来解决这类问题。虽然增加缓冲区大小可能会缓解问题,但它只是掩盖了问题的本质,并没有真正解决并发逻辑上的错误。在开发中,尽量使用无缓冲的通道,这样更容易地发现并发问题。解决方案暂时一:在订阅者中发送退出信号

修改ListenOnChannel函数,在接收到数据后向退出通道信号发送。同时,在主函数中等待所有订阅者func (s *Subscriber) ListenOnChannel(quit chan int) { data := lt;-s.Channel fmt.Printf(quot;Name: v; Data: v\nquot;, s.Name, data) quit lt;- 0 // 订阅者完成后发送退出信号}func main() { quit := make(chan int, 3) // 带缓冲的退出频道 p := amp;Publisher{}subscribers := []*Subscriber{ {Channel: make(chan int), Name: quot;1quot;}, {Channel: make(chan int), Name: quot;2quot;}, {Channel: make(chan int), Name: quot;3quot;}, } for _, v := rangeSubscribers { p.Sub(v.Channel) go v.ListenOnChannel(quit) // 提交退出频道 } p.Pub(2, quit) for i := 0; i lt; len(订阅者); i { lt;-quit // 等待所有订阅者完成}}登录后复制

在本方案中,退出通道的缓冲区大小需要设置为3,因为需要接收来自三个订阅者的信号。

方案二:使用sync.WaitGroup

sync.WaitGroup提供了更优雅的方式来等待一组Goroutine完成。package mainimport ( quot;fmtquot; quot;syncquot;)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 lt;- m } // 关闭所有频道,通知订阅者结束 for _, c := range p.listeners { close(c) }}func (s *Subscriber) ListenOnChannel(wg *sync.WaitGroup) { defer wg.Done() // Goroutine结束时调用Done for data := range s.Channel { // 使用范围接收数据,通道关闭后循环结束 fmt.Printf(quot;Name: v; Data: v\nquot;, s.Name, data) }}func main() { var wgsync.WaitGroup p := amp;Publisher{} 订阅者:= []*Subscriber{ {Channel: make(chan int), Name: quot;1quot;}, {Channel: make(chan int), Name: quot;2quot;}, {Channel: make(chan int), Name: quot;3quot;}, } for _, v := rangesubscribers { p.Sub(v.Channel) wg.Add(1) // 启动一个 Goroutine 前调用 Add go v.ListenOnChannel(amp;wg) // 传递 WaitGroup } p.Pub(2) wg.Wait() // 等待所有 Goroutine 完成}登录后复制

在这个方案中,我们使用sync.WaitGroup来跟踪所有订阅者Goroutine的状态。在每个订阅者Goroutine启动前,调用wg.Add(1)增加计数器。在Goroutine结束时,调用wg.Done()减少计数器。

main函数调用wg.Wait()等待计数归零,即所有Goroutine完成。

同时,我们使用close(c)关闭了所有的通道,这样订阅者就可以通过范围循环接收数据,当通道关闭时,循环自动结束。总结

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

以上就是Go虹编程:Goroutine死锁问题的详细内容,更多请关注乐哥常识网其他相关文章!

Go 并发编程:解决
css背景图片全屏铺满自适应 css背景图自适应屏幕大小
相关内容
发表评论

游客 回复需填写必要信息