锁是异步中进行协调使用的常用工具,无论是gcd还是其他,都有用过。
而每一种语言的锁都是通用的,比如说互斥,读写等
死锁
- 不是锁的一种,是错误使用锁的现象
常见死锁:- 单go程使用同一个channel自读自写
- 多个go程使用channel通信,go程创建之前,对channel进行读写
- 多个go程使用多个channel通信,相互依赖造成死锁
- 多个go程使用锁(读写锁、互斥锁)和channel通信
互斥锁 mutex
- 作用:保护公共区,防止多个控制流共同访问共享资源时造成数据混乱
- 建议锁,不具备强制性
- 使用注意事项:
- 访问公共区之前,加锁。访问结束立即结束
- 锁的粒度越小越好
- 使用方法:
- 创建互斥量
var mutex sync.Mutex - 加锁
mutex.Lock()- …….访问公共区
- 解锁
mutex.Unlock()
- 创建互斥量
读写锁
- 特性:
- 读时共享,写时独占
- 写锁优先级高
- 读写锁只有一把,但具有两种属性(r/w) 加锁时根据使用的需求选择加锁属性
- 使用方法:
- 创建互斥量
var mutex sync.RWMutex - 加读锁
mutex.RLock()加写锁mutex.Lock()- …….访问公共区
- 解读锁
mutex.RUnlock()解写锁mutex.RUnlock()
- 创建互斥量
- 建议:尽量不要将锁和channel混合使用——条件变量
条件变量
- ==条件变量不是锁的一种==!要结合锁使用
条件变量所使用的函数
-
wait方法
-
func (c *Cond)wait()- 阻塞等待条件变量满足
- 释放已经掌握的互斥锁,要求在调用wait之前,先加锁 ……阻塞等待条件变量满足
- 被唤醒时,解除阻塞,重新加锁
-
-
signal方法
-
func (c*Cond)Signal()- 唤醒阻塞在条件变量上的go程
-
-
broadcast方法
-
func (c*Cond)Broadcast()- 唤醒阻塞在条件变量上的所有go程
-
条件变量实现步骤:
创建条件变量 var cond sync.Cond
初始化条件变量的成员L,指定锁(互斥,读写)
cond.L = new(sync.Mutex)加锁
cond.L.Lock()-
判断条件变量满足,调用wait函数
for len(ch) == cap(ch){ //此处必须使用for 循环判断 wait是否满足条件 cond.wait() } 产生数据,写入公共区
唤醒阻塞在条件变量上的对端
cond.Signal()解锁
cond.L.UnLock()
示例
//创建条件变量
var cond sync.Cond
// 进行单向读管道
func userR(r <-chan int) {
for { // 死循环读取
cond.L.Lock() //必须要先加锁
for len(r) == 0 { // 官方推荐使用for判断,用if会执行一段时间后阻塞
cond.Wait()
}
fmt.Println("=====读:",<-r)
cond.Signal() // 唤醒写端
cond.L.Unlock() //解锁
}
}
//同读端
func userW(w chan<- int) {
for {
cond.L.Lock()
for cap(w) == len(w) {
cond.Wait()
}
rang := rand.Intn(3000)
w <- rang
fmt.Println("--写",rang)
cond.Signal()
cond.L.Unlock()
}
}
func Ch() {
rand.Seed(time.Now().UnixNano())
ch := make(chan int, 5)
cond.L = new(sync.Mutex)
for i := 0; i <= 1000; i++ {
go userR(ch)
go userW(ch)
}
for {
//防止主函数结束,子函数跟着销毁
runtime.GC()
}
}