参考:
https://blog.csdn.net/li_101357/article/details/80286549(CSDN:sync包)
https://zhuanlan.zhihu.com/p/138214620(知乎:sync包应用详解)
https://studygolang.com/articles/3373(Mutex和rwmutex的区别)
在并发编程中同步原语也就是我们通常说的锁的主要作用是保证多个线程或者 goroutine
在访问同一片内存时不会出现混乱的问题。Go
语言的sync
包提供了 Mutex
、RWMutex
、WaitGroup
、Once
和 Cond
这些同步原语的实现原理。
本片只聚焦sync
包里这些同步原语的应用场景,同时也会介绍sync
包中的Pool
和Map(另外整理)
的应用场景和使用方法。
sync.Mutex(互斥锁)
func mutex() { type safeInt struct{ &sync.Mutex Num int } count:=&safeInt{} done:=make(chan bool) for i:=0;i<10000;i++{ go func(i int) { count.Lock() count.Num+=i count.Unlock() done<-true }(i) } for i:=0;i<10000;i++ { <-done } fmt.Println(count.Num) }
sync.RWMutex(读写锁)
func rwMutex() { type safeInt struct{ &sync.RWMutex Num int } count:=&safeInt{} done:=make(chan bool) for i:=0;i<10000;i++{ go func(i int) { count.Lock()//写锁 count.Num+=i count.Unlock()//写锁 done<-true }(i) } for i:=0;i<10000;i++ { <-done //为了主协程等待 } fmt.Println(count.Num) }
sync.WaitGroup(等待组)
func waitGroup() { wg := &sync.WaitGroup{} for i := 0; i < 8; i++ { wg.Add(1) go func(i int) { // Do something fmt.Println(i) wg.Done() }(i) } wg.Wait() fmt.Println("done") }
sync.Once(执行一次)
func once() { once:=&sync.Once{} ch:=make(chan bool) for i:=0;i<10;i++ { go func() { once.Do(func() { fmt.Println("我自会执行一次") }) ch<-true }() } for i:=0;i<10;i++ { <-ch } }
sync.Cond(条件变量)
在 Wait 之前应当手动为 c.L 上锁,Wait 结束后手动解锁。为避免虚假唤醒,需要将 Wait 放到一个条件判断循环中。官方要求的写法如下:
//控制方法 cond:=sync.NewCond(&mutex):生成一个cond,需要传入一个mutex,因为阻塞等待通知的操作以及通知解除阻塞的操作就是基于sync.Mutex来实现的。 cond.Wait():用于等待通知 cond.Signal():用于发送单个通知 cond.Broadcat():用于广播 //使用方式 cond.L.Lock() for !condition{ cond.Wait() } cond.L.UnLock() //满足条件后的后续逻辑
举例
func cond() { var locker sync.Mutex var cond = sync.NewCond(&locker) //等价于 var cond = &sync.Cond{L:&sync.Mutex{}} //locker.Lock() for i := 0; i < 10; i++ { go func(x int) { cond.L.Lock() // 获取锁 defer cond.L.Unlock() // 释放锁 cond.Wait() // 等待通知,阻塞当前 goroutine // 通知到来的时候, cond.Wait()就会结束阻塞, do something. 这里仅打印 fmt.Println(x) }(i) } time.Sleep(time.Second * 1) // 睡眠 1 秒,等待所有 goroutine 进入 Wait 阻塞状态 fmt.Println("Signal...") cond.Signal() // 1 秒后下发一个通知给已经获取锁的 goroutine time.Sleep(time.Second * 1) fmt.Println("Signal...") cond.Signal() // 1 秒后下发下一个通知给已经获取锁的 goroutine time.Sleep(time.Second * 1) cond.Broadcast() // 1 秒后下发广播给所有等待的goroutine fmt.Println("Broadcast...") time.Sleep(time.Second * 1) // 睡眠 1 秒,等待所有 goroutine 执行完毕 }
func cond2() {
mutex := sync.Mutex{}
var cond = sync.NewCond(&mutex)
mail := 0
go func() {
for count := 0; count <= 5; count++{
time.Sleep(time.Second)
mail = count
cond.Broadcast()
}
}()
//为什么使用for而不是if,if会同时上锁,广播后会同时解锁
//broadcast的时候,会通知到所有的worker,此时wait都会解除,但并不是所有的worker都满足通知条件的,所以加一个for循环,不满足通知条件的会再次wait。
// worker1
go func() {
cond.L.Lock()
for mail != 1 { // 触发的条件,如果不等于1,就会进入cond.Wait()等待,此时cond.Broadcast()通知进来的时候,wait阻塞解除,进入下一个循环,此时发现mail == 1,跳出循环,开始工作。
cond.Wait()//会阻塞
}
cond.L.Unlock()
fmt.Println("worker1 started to work")
time.Sleep(3*time.Second)
fmt.Println("worker1 work end")
}()
// worker2
go func() {
cond.L.Lock()
for mail != 4 {
cond.Wait()
}
cond.L.Unlock()
fmt.Println("worker2 started to work")
time.Sleep(3*time.Second)
fmt.Println("worker2 work end")
}()
// worker3
go func() {
cond.L.Lock()
for mail != 5 {
cond.Wait()
}
cond.L.Unlock()
fmt.Println("worker3 started to work")
time.Sleep(3*time.Second)
fmt.Println("worker3 work end")
}()
time.Sleep(10*time.Second)
}
sync.Map
参考:https://blog.csdn.net/u010230794/article/details/82143179(csdn:sync.Map的使用和介绍)
利用传统的sync.RWMutex+Map实现并发安全的map
//传统方式并发安全的map var rwmap = struct { sync.RWMutex m map[string]string }{m:make(map[string]string)} //写数据时 rwmap.Lock() rwmap.m["key"]="value123" rwmap.Unlock() //读数据时 rwmap.RLock() val:=rwmap.m["key"] rwmap.RUnlock() fmt.Println(val)
使用sync.Map实现
func syncmap() { m := &sync.Map{} // 添加元素 m.Store(1, "one") m.Store(2, "two") // 获取元素1 value, contains := m.Load(1) if contains { fmt.Printf("%s ", value.(string)) } // 返回已存value,否则把指定的键值存储到map中 value, loaded := m.LoadOrStore(3, "three") if !loaded { fmt.Printf("%s ", value.(string)) } m.Delete(3) // 迭代所有元素 m.Range(func(key, value interface{}) bool { fmt.Printf("%d: %s ", key.(int), value.(string)) return true }) }