go语法:sync
参考:
https://blog.csdn.net/li_101357/article/details/80286549(CSDN:sync包)
https://zhuanlan.zhihu.com/p/365552668(sync源码分析)
https://zhuanlan.zhihu.com/p/138214620(知乎:sync包应用详解)
https://studygolang.com/articles/3373(Mutex和rwmutex的区别)
https://blog.csdn.net/weixin_43753680/article/details/114363122(互斥锁原理)(高水平文章)
在并发编程中同步原语也就是我们通常说的锁的主要作用是保证多个线程或者 goroutine
在访问同一片内存时不会出现混乱的问题。Go
语言的sync
包提供了 Mutex
、RWMutex
、WaitGroup
、Once
和 Cond
这些同步原语的实现原理。
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) }
正常模式(非公平):刚开始都是正常模式
g1持有锁的时候,新进g2会自旋,自选4次后如果失败,将进入等待队列(fifo)阻塞并等待唤醒,
被唤醒的第一个g2并不是直接获取锁,而是会和新进的g3竞争,但很容易失败因为g3正拥有时间片
饥饿模式(公平):如果有协程超过1ms没有获取到锁,就会进入饥饿模式
会把锁直接教导队列里面的第一个g,新进的g不会参与抢锁,并且也不会自旋而是直接进入队列尾部
如果等待队列已清空又会回到正常模式
sync.RWMutex(读写锁)
1适合读多写少的场景
2内含一个互斥锁
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\n", value.(string)) } // 返回已存value,否则把指定的键值存储到map中 value, loaded := m.LoadOrStore(3, "three") if !loaded { fmt.Printf("%s\n", value.(string)) } m.Delete(3) // 迭代所有元素 m.Range(func(key, value interface{}) bool { fmt.Printf("%d: %s\n", key.(int), value.(string)) return true }) }