目录
- sync.Map 并发安全的Map
- sync.Once 只执行一次
- sync.Cond 条件变量控制
- 小结
sync.Map 并发安全的Map
Goroutine
func unsafeMap(){
var wg sync.WaitGroup
m := make(map[int]int)
wg.Add(2)
go func() {
defer wg.Done()
for i := 0; i < 10000; i++ {
m[i] = i
}
}()
go func() {
defer wg.Done()
for i := 0; i < 10000; i++ {
fmt.Println(m[i])
}
}()
wg.Wait()
}
执行报错:
0
fatal error: concurrent map read and map write
goroutine 7 [running]:
runtime.throw({0x10a76fa, 0x0})
......
Map
func safeMap() {
var wg sync.WaitGroup
var m sync.Map
wg.Add(2)
go func() {
defer wg.Done()
for i := 0; i < 10000; i++ {
m.Store(i, i)
}
}()
go func() {
defer wg.Done()
for i := 0; i < 10000; i++ {
fmt.Println(m.Load(i))
}
}()
wg.Wait()
}
makeStoreLoadLoadOrStoreDeleteRange
sync.Once 只执行一次
很多场景下我们需要确保某些操作在高并发的场景下只执行一次,例如只加载一次配置文件、只关闭一次通道等。
initpackage
sync.Once
sync.Once
- 当且仅当第一次访问某个变量时,进行初始化(写);
- 变量初始化过程中,所有读都被阻塞,直到初始化完成;
- 变量仅初始化一次,初始化完成后驻留在内存里。
var loadOnce sync.Once
var x int
for i:=0;i<10;i++{
loadOnce.Do(func() {
x++
})
}
fmt.Println(x)
输出
1
sync.Cond 条件变量控制
sync.Cond
sync.Mutexsync.Condgoroutine
sync.Cond
channelselect
创建实例
func NewCond(l Locker) *Cond
NewCondCond
广播唤醒所有
func (c *Cond) Broadcast()
Broadcastcgoroutine
唤醒一个协程
func (c *Cond) Signal()
Signalcgoroutine
等待
func (c *Cond) Wait()
每个 Cond 实例都会关联一个锁 L(互斥锁 *Mutex,或读写锁 *RWMutex),当修改条件或者调用 Wait 方法时,必须加锁。
举个不恰当的例子,实现一个经典的生产者和消费者模式,但有先决条件:
- 边生产边消费,可以多生产多消费。
- 生产后通知消费。
- 队列为空时,暂停等待。
- 支持关闭,关闭后等待消费结束。
- 关闭后依然可以生产,但无法消费了。
var (
cnt int
shuttingDown = false
cond = sync.NewCond(&sync.Mutex{})
)
cntshuttingDowncond
生产者
func Add(entry int) {
cond.L.Lock()
defer cond.L.Unlock()
cnt += entry
fmt.Println("生产咯,来消费吧")
cond.Signal()
}
消费者
func Get() (int, bool) {
cond.L.Lock()
defer cond.L.Unlock()
for cnt == 0 && !shuttingDown {
fmt.Println("未关闭但空了,等待生产")
cond.Wait()
}
if cnt == 0 {
fmt.Println("关闭咯,也消费完咯")
return 0, true
}
cnt--
return 1, false
}
关闭程序
func Shutdown() {
cond.L.Lock()
defer cond.L.Unlock()
shuttingDown = true
fmt.Println("要关闭咯,大家快消费")
cond.Broadcast()
}
主程序
var wg sync.WaitGroup
wg.Add(2)
time.Sleep(time.Second)
go func() {
defer wg.Done()
for i := 0; i < 10; i++ {
go Add(1)
if i%5 == 0 {
time.Sleep(time.Second)
}
}
}()
go func() {
defer wg.Done()
shuttingDown := false
for !shuttingDown {
var cur int
cur, shuttingDown = Get()
fmt.Printf("当前消费 %d, 队列剩余 %d
", cur, cnt)
}
}()
time.Sleep(time.Second * 5)
Shutdown()
wg.Wait()
- 分别创建生产者与消费者。
- 生产10个,每5个休息1秒。
- 持续消费。
- 主程序关闭队列。
输出
生产咯,来消费吧
当前消费 1, 队列剩余 0
未关闭但空了,等待生产
生产咯,来消费吧
生产咯,来消费吧
当前消费 1, 队列剩余 1
当前消费 1, 队列剩余 0
未关闭但空了,等待生产
生产咯,来消费吧
生产咯,来消费吧
生产咯,来消费吧
当前消费 1, 队列剩余 2
当前消费 1, 队列剩余 1
当前消费 1, 队列剩余 0
未关闭但空了,等待生产
生产咯,来消费吧
生产咯,来消费吧
生产咯,来消费吧
生产咯,来消费吧
当前消费 1, 队列剩余 1
当前消费 1, 队列剩余 2
当前消费 1, 队列剩余 1
当前消费 1, 队列剩余 0
未关闭但空了,等待生产
要关闭咯,大家快消费
关闭咯,也消费完咯
当前消费 0, 队列剩余 0
小结
1.sync.Map 并发安全的Map。
2.sync.Once 只执行一次,适用于配置读取、通道关闭。
3.sync.Cond 控制协调共享资源。