Golang协程同步技巧与WaitGroup用法详解
时间:2025-06-27 09:23:19 417浏览 收藏
大家好,我们又见面了啊~本文《Golang协程同步技巧与WaitGroup使用方法》的内容中将会涉及到等等。如果你正在学习Golang相关知识,欢迎关注我,以后会给大家带来更多Golang相关文章,希望我们能一起进步!下面就开始本文的正式内容~
Golang协程同步的方法包括WaitGroup、Mutex、RWMutex、Channel、Cond和Atomic。1. WaitGroup用于等待一组协程完成,通过Add、Done、Wait三个方法实现;2. Mutex和RWMutex用于保护共享资源,前者提供独占锁,后者支持读写锁;3. Channel用于协程间通信与同步,适合生产者-消费者模型;4. Cond用于条件变量,常配合Mutex使用;5. Atomic用于原子操作基本数据类型,避免数据竞争。选择合适的同步方式需根据具体场景判断,如简单计数用Atomic,等待任务完成用WaitGroup,共享资源保护用Mutex或RWMutex,复杂条件触发用Cond,协程通信用Channel。
Golang协程同步,简单来说就是确保多个协程按照我们期望的顺序或方式执行,避免数据竞争和死锁等问题。WaitGroup 是一个常用的工具,但用对了和用错了,效果可是天差地别。

WaitGroup使用技巧

WaitGroup,顾名思义,就是等待一组协程完成。它主要有三个方法:Add(delta int)
、Done()
和 Wait()
。Add
用于设置需要等待的协程数量,Done
用于通知 WaitGroup 一个协程已完成,Wait
用于阻塞直到所有协程都完成。
最常见的错误用法就是 Add
的位置不对。很多人喜欢在启动协程 之后 调用 Add
,这在某些情况下可能会导致 Wait
提前返回,因为协程启动需要时间,Add
的调用可能晚于 Wait
的执行。

正确的做法是在启动协程 之前 调用 Add
,确保 WaitGroup 能够正确地追踪所有协程。
package main import ( "fmt" "sync" "time" ) func main() { var wg sync.WaitGroup numWorkers := 5 for i := 0; i < numWorkers; i++ { wg.Add(1) // 在启动协程之前 Add go func(id int) { defer wg.Done() // 确保协程退出时 Done fmt.Printf("Worker %d starting\n", id) time.Sleep(time.Duration(id) * time.Second) // 模拟工作 fmt.Printf("Worker %d finished\n", id) }(i) } wg.Wait() // 等待所有协程完成 fmt.Println("All workers finished!") }
这个例子中,wg.Add(1)
放在了 go func
之前,保证了 WaitGroup 正确计数。defer wg.Done()
则保证了即使协程发生 panic,也能正确地调用 Done
,避免死锁。
除了基本的用法,WaitGroup 还可以结合 channel 使用,实现更复杂的同步逻辑。
Golang中除了WaitGroup还有哪些协程同步方法?
除了 WaitGroup
,Golang 还提供了 Mutex
(互斥锁)、RWMutex
(读写锁)、Channel
(通道)、Cond
(条件变量)和 Atomic
(原子操作)等多种协程同步方法。选择哪种方法取决于具体的应用场景。
- Mutex 和 RWMutex: 用于保护共享资源,防止多个协程同时访问。Mutex 提供独占锁,RWMutex 提供读写锁,允许多个协程同时读取,但只允许一个协程写入。
- Channel: 用于协程之间的通信和同步。通过发送和接收数据,可以实现协程之间的协作和数据共享。
- Cond: 用于在满足特定条件时唤醒等待的协程。Cond 通常与 Mutex 结合使用,用于实现复杂的同步逻辑。
- Atomic: 用于原子地操作基本数据类型,避免数据竞争。Atomic 操作通常比 Mutex 更高效,但只适用于简单的同步场景。
例如,如果需要保护一个计数器,可以使用 Mutex:
package main import ( "fmt" "sync" "time" ) var counter int var mutex sync.Mutex func incrementCounter() { for i := 0; i < 1000; i++ { mutex.Lock() counter++ mutex.Unlock() } } func main() { var wg sync.WaitGroup numWorkers := 5 for i := 0; i < numWorkers; i++ { wg.Add(1) go func() { defer wg.Done() incrementCounter() }() } wg.Wait() fmt.Println("Counter:", counter) // 预期结果:5000 }
而如果需要实现生产者-消费者模型,可以使用 Channel:
package main import ( "fmt" "time" ) func producer(ch chan int) { for i := 0; i < 10; i++ { ch <- i time.Sleep(time.Millisecond * 100) } close(ch) // 关闭 channel,通知消费者 } func consumer(ch chan int) { for num := range ch { // 从 channel 接收数据,直到 channel 关闭 fmt.Println("Received:", num) } } func main() { ch := make(chan int, 5) // 创建一个 buffered channel go producer(ch) go consumer(ch) time.Sleep(time.Second * 2) // 等待一段时间 }
Golang协程同步出现死锁怎么办?
死锁是协程同步中常见的问题,通常发生在多个协程互相等待对方释放资源时。解决死锁的关键在于避免循环等待,并合理地设计锁的获取顺序。
- 避免循环等待: 确保协程获取锁的顺序一致。如果多个协程都需要获取锁 A 和锁 B,那么所有协程都应该先获取锁 A,再获取锁 B。
- 设置超时时间: 在获取锁时设置超时时间,避免无限等待。如果超时,则释放已获取的锁,并重试。
- 使用
go vet
:go vet
是 Golang 内置的静态代码分析工具,可以检测潜在的死锁问题。 - 使用
pprof
:pprof
是 Golang 的性能分析工具,可以分析程序的运行状态,找出死锁的协程。
例如,下面的代码演示了一个简单的死锁场景:
package main import ( "fmt" "sync" "time" ) var mutexA sync.Mutex var mutexB sync.Mutex func routineA() { mutexA.Lock() defer mutexA.Unlock() time.Sleep(time.Millisecond * 100) // 模拟工作 mutexB.Lock() // 等待 mutexB,可能导致死锁 defer mutexB.Unlock() fmt.Println("Routine A") } func routineB() { mutexB.Lock() defer mutexB.Unlock() time.Sleep(time.Millisecond * 100) // 模拟工作 mutexA.Lock() // 等待 mutexA,可能导致死锁 defer mutexA.Unlock() fmt.Println("Routine B") } func main() { go routineA() go routineB() time.Sleep(time.Second * 1) // 等待一段时间 }
在这个例子中,routineA
先获取 mutexA
,再尝试获取 mutexB
,而 routineB
先获取 mutexB
,再尝试获取 mutexA
,这就形成了循环等待,导致死锁。
避免死锁的方法是让两个 routine 按照相同的顺序获取锁:
package main import ( "fmt" "sync" "time" ) var mutexA sync.Mutex var mutexB sync.Mutex func routineA() { mutexA.Lock() defer mutexA.Unlock() time.Sleep(time.Millisecond * 100) // 模拟工作 mutexB.Lock() defer mutexB.Unlock() fmt.Println("Routine A") } func routineB() { mutexA.Lock() // 先获取 mutexA defer mutexA.Unlock() time.Sleep(time.Millisecond * 100) // 模拟工作 mutexB.Lock() defer mutexB.Unlock() fmt.Println("Routine B") } func main() { go routineA() go routineB() time.Sleep(time.Second * 1) // 等待一段时间 }
通过让 routineB
也先获取 mutexA
,避免了循环等待,从而解决了死锁问题。
如何选择合适的Golang协程同步方法?
选择合适的协程同步方法,需要根据具体的应用场景和需求进行权衡。
- 简单的数据竞争: 如果只是需要保护简单的基本数据类型,可以使用
Atomic
操作。 - 共享资源的并发访问: 如果需要保护共享资源,可以使用
Mutex
或RWMutex
。如果读操作远多于写操作,可以使用RWMutex
,提高并发性能。 - 协程之间的通信和同步: 如果需要协程之间进行通信和同步,可以使用
Channel
。 - 复杂的条件同步: 如果需要在满足特定条件时唤醒等待的协程,可以使用
Cond
。 - 等待一组协程完成: 如果需要等待一组协程完成,可以使用
WaitGroup
。
没有绝对的最佳实践,只有最适合特定场景的方案。理解各种同步方法的优缺点,并根据实际情况进行选择,是编写高效并发程序的关键。
以上就是《Golang协程同步技巧与WaitGroup用法详解》的详细内容,更多关于Golang协程的资料请关注golang学习网公众号!
-
505 收藏
-
502 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
228 收藏
-
480 收藏
-
388 收藏
-
340 收藏
-
121 收藏
-
140 收藏
-
252 收藏
-
425 收藏
-
279 收藏
-
295 收藏
-
436 收藏
-
445 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 508次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习