把经典的同步工具总结一下
使用方法:
1 使用cond := sync.NewCond()来创建,入参必须是Locker类型,参考用例2 传递cond时必须是引用,或者取地址,千万不要值传递3 然后加锁,使用wait()等待通知,最后再解锁,wait最好在for循环里面操作.4 使用Signal()和Broadcast()方法发送通知粗浅原理:
sync.Cond源码结构 type Cond struct { // noCopy可以嵌入到结构中,在第一次使用后不可复制,使用go vet作为检测使用 noCopy noCopy // 根据需求初始化不同的锁,如*Mutex 和 *RWMutex L Locker // 通知列表,调用Wait()方法的goroutine会被放入list中,每次唤醒,从这里取出 notify notifyList // 复制检查,检查cond实例是否被复制 checker copyChecker } 当我们创建了cond的实例以后,调用wait方法,在wait 中会先对cond检查是不是值传递,然后将当前所在goroutine方法notifyList(通知列表中),接着加锁 Wait()函数源码如下: func (c *Cond) Wait() { // 检查c是否是被复制的,如果是就panic c.checker.check() // 将当前goroutine加入等待队列 t := runtime_notifyListAdd(&c.notify) // 解锁 c.L.Unlock() // 等待队列中的所有的goroutine执行等待唤醒操作 runtime_notifyListWait(&c.notify, t) c.L.Lock() } 两个通知函数的源码 func (c *Cond) Signal() { // 检查c是否是被复制的,如果是就panic c.checker.check() // 通知等待列表中的一个 runtime_notifyListNotifyOne(&c.notify) } func (c *Cond) Broadcast() { // 检查c是否是被复制的,如果是就panic c.checker.check() // 唤醒等待队列中所有的goroutine runtime_notifyListNotifyAll(&c.notify) }疑问 这里摘抄了郝大哥的Go语言核心36讲中的两个问题:
为什么先要锁定条件变量基于的互斥锁,才能调用它的Wait方法?因为条件变量的Wait方法在阻塞当前的 goroutine 之前,会解锁它基于的互斥锁,所以在调用该Wait方法之前,我们必须先锁定那个互斥锁,否则在调用这个Wait方法时,就会引发一个不可恢复的 panic。 为什么条件变量的Wait方法要这么做呢?你可以想象一下,如果Wait方法在互斥锁已经锁定的情况下,阻塞了当前的 goroutine,那么又由谁来解锁呢?别的 goroutine 吗? 先不说这违背了互斥锁的重要使用原则,即:成对的锁定和解锁,就算别的 goroutine 可以来解锁,那万一解锁重复了怎么办?由此引发的 panic 可是无法恢复的。 如果当前的 goroutine 无法解锁,别的 goroutine 也都不来解锁,那么又由谁来进入临界区,并改变共享资源的状态呢?只要共享资源的状态不变,即使当前的 goroutine 因收到通知而被唤醒,也依然会再次执行这个Wait<方法,并再次被阻塞。 所以说,如果条件变量的Wait方法不先解锁互斥锁的话,那么就只会造成两种后果:不是当前的程序因 panic 而崩溃,就是相关的 goroutine 全面阻塞。
为什么要用for语句来包裹调用其Wait方法的表达式,用if不可以吗?显然,if语句只会对共享资源的状态检查一次,而for语句却可以做多次检查,直到这个状态改变为止。那为什么要做多次检查呢? 这主要是为了保险起见。如果一个 goroutine 因收到通知而被唤醒,但却发现共享资源的状态,依然不符合它的要求,那么就应该再次调用条件变量的Wait方法,并继续等待下次通知的到来。 这种情况是很有可能发生的
测试例子
package main import ( "fmt" "sync" "time" ) func main() { var locker = new(sync.Mutex) cond := sync.NewCond(locker)//cond最后返回的必须是指针,入参也要是指针 var wg sync.WaitGroup for i:=0;i<40;i++ { wg.Add(1) go func(x int) { cond.L.Lock() defer cond.L.Unlock() cond.Wait() fmt.Println("我等到了通知,我输出:",x) wg.Done() }(i) } time.Sleep(time.Second) fmt.Println("我现在通知其中一个........") cond.Signal() time.Sleep(time.Second) fmt.Println("我再次通知其中一个........") cond.Signal() time.Sleep(time.Second) fmt.Println("我通知所有的..........") cond.Broadcast() wg.Wait() }重点
变量类型从头到尾一致,这也是与锁区别的其中一点 package main import ( "fmt" "sync/atomic" ) func main() { i32 := new(int32) //加法操作 向地址中原值做加法操作,并且返回新值 atomic.AddInt32(i32, 32) addInt := atomic.AddInt32(i32, 2) fmt.Println("addInt : ", addInt) //保存指针的类型做加法操作 uiptr := new(uintptr) uiptrval := atomic.AddUintptr(uiptr, 12) uiptrval = atomic.AddUintptr(uiptr, 23) fmt.Println("uiptrval : ", uiptrval) //减法操作 u开头的类型不能做减法操作 addInt = atomic.AddInt32(i32, -32) fmt.Println("addInt : ", addInt) //CAS 交换值 old值必须和地址中的旧值一样 bool := atomic.CompareAndSwapInt32(i32, 2, 23) fmt.Println("bool : ", bool) fmt.Println("addInt : ", *i32) //获取值 将地址中的值取出来 addInt = atomic.LoadInt32(i32) fmt.Println("addInt : ", addInt) //存值或者更改值 atomic.StoreInt32(i32,100) fmt.Println("StoreInt32 : ", *i32) //存值或者更改值 并把旧值返回 oldInt := atomic.SwapInt32(i32,200) fmt.Println("oldInt : ", oldInt) //原子值 可以放任意类型 但是不能放置nil类型 var atomicValue atomic.Value sli := []string{"1","2","3"} atomicValue.Store(sli)//存储进去,这样存储进去是不安全的,切片是引用传递,可以在外面更改切片的值,可以拷贝一份再放进去 //更改他 sli[0] = "更改他" fmt.Println("已经被更改",atomicValue.Load()) //复制一份 copySlice := make([]string,len(sli)) copy(copySlice,sli) atomicValue.Store(copySlice) //更改他 发现无法被更改回来. sli[0] = "1" fmt.Println(atomicValue.Load()) }