前言
我也不是很懂sync.Cond用来干什么的,才学,主要是没用过没见过,学着很懵逼,建议观看大佬
【【Go语言面试知识点】 Goroutine - sync.Cond 从社区入手,了解高并发下的单播/多播实现】https://www.bilibili.com/video/BV1f64y1F72X?vd_source=8e14beefe0703b37528893bf5b340f80
sync.WaitGroup
入门
sync.WaitGroup 是 Go 语言标准库中的一个并发工具,用于管理一组 goroutine 的同步。它提供了三个主要的方法:Add、Done 和 Wait。
- Add 方法用于添加需要等待的 goroutine 的数量,它接受一个整数参数表示要等待的 goroutine 的数量。
- Done 方法用于表示一个 goroutine 已经执行完毕,它将等待的数量减去 1。
- Wait 方法会阻塞当前的 goroutine,直到等待的数量为 0。
WaitGroup 的主要作用是确保在一组 goroutine 执行完之前,当前的 goroutine 不会退出或继续执行后续的代码。
通过正确使用 Add、Done 和 Wait 方法,可以实现并发任务的同步,等待所有的任务完成后再进行后续的处理。
package _case
import (
"fmt"
"sync"
"time"
)
func WaitGroupCase() {
var a, b = 1000, 1000
start := time.Now()
for i := 0; i < 500000000; i++ {
multi(a, b)
}
t := time.Since(start) //计算耗时时间
fmt.Println(t)
start = time.Now()
wg := sync.WaitGroup{}
for i := 0; i < 30; i++ {
wg.Add(1) //计数器加1
go func() { //采用闭包的方式
defer wg.Done() //计数器-1
for j := 0; j < 500000000; j++ {
multi(a, b)
}
}()
}
wg.Wait() //等待协程
t = time.Since(start)
fmt.Println("多协程", t)
}
func multi(a, b int) int {
return a * b
}
作用
- 等待一组协程完成
- 工作原理: 通过计数器来或偶去协程的完成情况
- 启动一个协程计数器 +1 协程退出时计数器 -1
- 通过wait方法阻塞主协程, 等待计数器清零后才能执行后续操作
陷阱
- 协程间传递时需要以指针的方式或闭包的方式引用 WaitGroup 对象,否则会造成死锁
应用场景
- 通过协程并行执行一组任务, 且任务全部完成后才能进行下一步操作
sync.Cond
入门
sync.Cond 是 Go 语言标准库中的一个条件变量类型,用于在多个 goroutine 之间进行同步和通信。
条件变量通过等待和通知机制来协调 goroutine 之间的操作。它提供了以下几个方法:
- Wait 方法会使当前的 goroutine 进入等待状态,并且释放相应的锁。当其他 goroutine 调用条件变量的 Signal 或 Broadcast 方法时,当前的 goroutine 会重新获得锁并继续执行。
- Signal 方法会通知一个等待在条件变量上的 goroutine,使其从等待状态变为运行状态。
- Broadcast 方法会通知所有等待在条件变量上的 goroutine,使它们从等待状态变为运行状态。
Wait 方法和 Signal、Broadcast 方法都需要在互斥锁的保护下进行调用。
条件变量主要用于在多个 goroutine 之间进行线程间同步和通信。它常用于解决生产者-消费者模型、任务调度和其他需要协调多个 goroutine 的场景。通过使用条件变量,我们可以更灵活地控制和同步 goroutine 的执行顺序和操作。
需要注意的是,使用条件变量时应避免死锁和竞态条件,确保互斥锁的正确使用以及对条件变量的正确等待和通知操作。
package _case
import (
"fmt"
"sync"
"time"
)
func CondCase() {
list := make([]int, 0)
//sync.NewCond(&sync.Mutex{}) 是用于创建一个条件变量(sync.Cond)
//需要和互斥锁(sync.Mutex)配合使用
cood := sync.NewCond(&sync.Mutex{})
go readList(&list, cood)
go readList(&list, cood)
go readList(&list, cood)
time.Sleep(time.Second * 10)
initList(&list, cood)
}
func initList(list *[]int, c *sync.Cond) {
//主叫方, 可以持有锁,也可以不持锁
c.L.Lock() //持锁
defer c.L.Unlock()
for i := 0; i < 10; i++ {
*list = append(*list, i)
}
//唤醒所有条件等待的协程
c.Broadcast() //全部打印
}
func readList(list *[]int, c *sync.Cond) {
//被叫方,必须持锁
c.L.Lock()
defer c.L.Unlock()
for len(*list) == 0 {
fmt.Printf("readList wait")
c.Wait()
}
fmt.Println("list: ", list)
}
type queue struct {
list []int
cond *sync.Cond
}
func newQueue() *queue {
q := &queue{
list: []int{},
cond: sync.NewCond(&sync.Mutex{}),
}
return q
}
func (q *queue) Put(item int) {
q.cond.L.Lock()
defer q.cond.L.Unlock()
q.list = append(q.list, item)
//当写入数据成功,唤醒一个协程来处理数据
q.cond.Signal() //随机打印一个
}
func (q *queue) GetMany(n int) []int {
q.cond.L.Lock()
defer q.cond.L.Unlock()
for len(q.list) < n {
q.cond.Wait()
}
list := q.list[:n]
q.list = q.list[n:]
return list
}
func CondQueueCase() {
q := newQueue()
var wg sync.WaitGroup
for n := 0; n < 10; n++ {
wg.Add(1)
go func(n int) {
list := q.GetMany(n)
fmt.Printf("%d: %d \n", n, list)
}(n)
}
}
作用
- 设置一组协程根据条件阻塞, 可以根据不同的条件阻塞
- 可以根据条件唤醒相应的协程
注意事项
- 被叫方必须持有锁
- 主叫方可以持有锁,但是允许不持有
- 尽可能减少无效唤醒