1. 功能对比
- WaitGroup:用于等待一组 goroutine 完成。它有一个计数器,每个需要等待的 goroutine 启动前调用
Add
方法增加计数器,完成时调用 Done
方法减少计数器,主线程通过 Wait
方法阻塞,直到计数器归零。
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
fmt.Printf("Goroutine %d is done\n", id)
}(i)
}
wg.Wait()
fmt.Println("All goroutines are done")
}
- Mutex:互斥锁,用于保护共享资源,确保同一时间只有一个 goroutine 可以访问该资源,防止竞态条件。
package main
import (
"fmt"
"sync"
)
var (
count int
mu sync.Mutex
)
func increment(wg *sync.WaitGroup) {
defer wg.Done()
mu.Lock()
count++
mu.Unlock()
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go increment(&wg)
}
wg.Wait()
fmt.Println("Final count:", count)
}
- Channel:用于 goroutine 之间的通信和同步。可以传递数据,并且可以通过关闭通道来通知接收方没有更多数据。
package main
import (
"fmt"
)
func main() {
ch := make(chan int)
go func() {
for i := 0; i < 3; i++ {
ch <- i
}
close(ch)
}()
for val := range ch {
fmt.Println("Received:", val)
}
}
2. 性能对比
- WaitGroup:性能主要消耗在计数器的原子操作上,在等待大量 goroutine 时,性能开销相对较小,因为它主要是等待,不涉及资源竞争。
- Mutex:在高并发场景下,如果频繁加锁解锁,会有一定的性能开销,因为涉及到操作系统的调度和上下文切换。
- Channel:性能取决于数据的传输量和 goroutine 的数量。如果数据传输频繁且量大,会有一定的性能损耗,因为涉及到数据的拷贝和同步。
3. 适用场景对比
- WaitGroup:适用于需要等待一组 goroutine 全部完成的场景,例如并行计算多个任务,最后汇总结果。
- Mutex:适用于保护共享资源,防止多个 goroutine 同时访问造成数据不一致,如计数器、共享内存等场景。
- Channel:适用于 goroutine 之间需要传递数据和同步的场景,如生产者 - 消费者模型。
4. 复杂并发场景下的选择
- 如果需要等待一组任务完成,使用 WaitGroup。
- 如果需要保护共享资源,使用 Mutex。
- 如果需要在 goroutine 之间传递数据和同步,使用 Channel。
- 在复杂场景下,可能会组合使用多种同步原语。例如,在一个生产者 - 消费者模型中,生产者和消费者之间通过 Channel 传递数据,同时如果消费者需要统计处理的数据量,可能会用到 Mutex 保护计数器,并且如果有多个生产者或消费者需要等待全部完成,可能会用到 WaitGroup。