本节源码位置 github.com/golang-mini…
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()
}
- 不需求
make
就能运用 - 还内置了
Store
、Load
、LoadOrStore
、Delete
、Range
等操作方法,自行体会。
sync.Once 只履行一次
很多场景下我们需求确保某些操作在高并发的场景下只履行一次,例如只加载一次配置文件、只封闭一次通道等。
init
函数是当地点的 package
初次被加载时履行,若迟迟未被运用,则既浪费了内存,又延长了程序加载时刻。
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.Mutex
通常用来维护临界区和同享资源,条件变量 sync.Cond
用来和谐想要拜访同享资源的 goroutine
。
也便是在存在同享变量时,能够直接运用sync.Cond
来和谐同享变量,比如最常见的同享行列,多消费多出产的形式。
我一开始也很疑问为什么不运用channel
和select
的形式来做出产者顾客模型(实际上也能够),这一节不是要点就不展开讨论了。
创立实例
func NewCond(l Locker) *Cond
NewCond
创立 Cond
实例时,需求相关一个锁。
广播唤醒一切
func (c *Cond) Broadcast()
Broadcast
唤醒一切等候条件变量 c
的 goroutine
,无需锁维护。
唤醒一个协程
func (c *Cond) Signal()
Signal
只唤醒恣意 1 个等候条件变量 c
的 goroutine
,无需锁维护。
等候
func (c *Cond) Wait()
每个 Cond 实例都会相关一个锁 L(互斥锁 *Mutex,或读写锁 *RWMutex),当修改条件或许调用 Wait 方法时,有必要加锁。
举个不恰当的例子,实现一个经典的出产者和顾客形式,但有先决条件:
- 边出产边消费,能够多出产多消费。
- 出产后告诉消费。
- 行列为空时,暂停等候。
- 支持封闭,封闭后等候消费完毕。
- 封闭后依然能够出产,但无法消费了。
var (
cnt int
shuttingDown = false
cond = sync.NewCond(&sync.Mutex{})
)
-
cnt
为行列,这儿直接用变量替代了,变量便是行列长度。 -
shuttingDown
消费封闭状态。 -
cond
现成的行列操控。
出产者
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 \n", 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
小结
- sync.Map 并发安全的Map。
- sync.Once 只履行一次,适用于配置读取、通道封闭。
- sync.Cond 操控和谐同享资源。
引证
- Go sync.Once
- k8s workerqueue源码