go语言提升(三):select、死锁、互斥锁、读写锁、条件变量、生产者消费者模型
1. select
1.1 select 概述
select是一个关键字。
-
select用于监听数据在channel上的流动
-
select监听channel是异步机制。
-
语法:
select {
case <- chan1:
// 如果chan1成功读到数据,则走该条语句
case <- chan2:
// 如果chan2成功读到数据,则走该条语句
default:
// 如果上面都没有成功,则进入default中的处理语句
}
- 特性
- 每一个分支,必须都是一个IO操作,(channel的r/w事件)
- 通常将select置于for循环中
- 一个case监听的channel不满足监听条件,当前case分支阻塞。
- 当所有的case分支都不满足条件时,如果select中包含default,则走default中的语句,否则select阻塞
- 当监听的多个case分支中,有多个case满足条件,那么会随机执行其中的一个。(每一个case可理解为一个go程)
- 为了防止忙轮询,可以适当放弃写default
- 当使用break时会跳出select
结论:所有使用select的go程和其他与其通信的go程是异步通信。
1.2 select简单举例
func main() {
ch := make(chan int)
quit := make(chan bool)
go func() {
for {
select {
case num := <-ch:
fmt.Println("子go程读到:", num)
case quit <- false:
fmt.Println("子go程退出")
}
}
}()
for i := 0; i < 10; i++ {
ch <- i + 1
}
<-quit
}
1.3 select实现斐波那契数列
func fibonacci(ch <-chan int, quit <-chan bool) {
for {
select {
case num := <-ch:
fmt.Println(num)
case <-quit:
return
}
}
}
func main() {
ch := make(chan int)
quit := make(chan bool)
x, y := 1, 1
go fibonacci(ch, quit)
for i := 0; i < 15; i++ {
ch <- x
x, y = y, x+y
}
quit<-false
}
1.4 select实现超时处理
步骤:
-
创建select,启动监听channel
case num := <-ch:
-
监听超时计时器:当其他分支满足时,该计时器会被时间重置
case <-time.After(time.Second*3):
-
当在计时时间内所有case都没有满足条件,那么则会执行计时器case内的代码以退出。
func main() {
ch := make(chan int)
quit := make(chan bool)
go func() {
for {
select {
case num := <-ch:
fmt.Println(num)
case <-time.After(time.Second * 3): // 每次只要有其他case语句满足了,都会讲这里的定时器重置
fmt.Println("超时退出")
quit <- false
runtime.Goexit()
}
}
}()
for i := 0; i < 2; i++ {
ch <- i
time.Sleep(time.Second * 2)
}
<-quit
}
2. 死锁
产生死锁的几种情况:
- 一个go程自己读channel又写channel
- 多个go程使用channel通信,在go程创建之前对channel读或者写
- 多个go程使用多个channel通信,互相依赖造成死锁
3. 锁
3.1 互斥锁(互斥量)mutex
类和方法在sync包下
定义互斥锁:var mutex sync.Mutex
- 使用注意事项:粒度要小
两个方法
func (m *Mutex) Lock()
func (m *Mutex) Unlock()
- go语言中的互斥锁和Linux下的使用基本一致,没什么可说的。
var mutex sync.Mutex
func printer(str string) {
// 加锁
mutex.Lock()
for _, ch := range str {
fmt.Printf("%c", ch)
time.Sleep(time.Millisecond * 300)
}
// 解锁
mutex.Unlock()
}
func user1() {
printer("hello")
}
func user2() {
printer("world")
}
func main() {
go user1()
go user2()
for {
;
}
}
3.2 读写锁
-
特性
- 读操作可以同时共享访问,写操作要互斥访问。
- 写锁优先级高(读锁和写锁同时要加锁时,写锁先加)
- 注意:不会抢占
-
锁只有一把,有两种属性(r/w)
-
定义以及使用
// 创建读写锁对象
var rwMutex RWMutex
// 写锁
func (rw *RWMutex) Lock()
func (rw *RWMutex) Unlock()
// 读锁
func (rw *RWMutex) RLock()
func (rw *RWMutex) RUnlock()
var count int
var rwLock sync.RWMutex
func readGo(index int) {
for {
rwLock.RLock()
fmt.Printf("----%d----读:%d\n", index, count)
rwLock.RUnlock()
time.Sleep(time.Millisecond * 300)
}
}
func writeGo(index int) {
for {
num := rand.Intn(500)
rwLock.Lock()
count = num
fmt.Printf("----%d----写:%d\n", index, count)
rwLock.Unlock()
time.Sleep(time.Millisecond * 400)
}
}
func main() {
rand.Seed(time.Now().UnixNano())
for i := 0; i < 5; i++ {
go readGo(i + 1)
}
for i := 0; i < 5; i++ {
go writeGo(i + 1)
}
for {
runtime.GC() // 防止被编译器优化掉空循环
}
}
4. 条件变量
条件变量要和锁一起使用
type Cond struct {
// 在观测或更改条件时L会冻结
L Locker
// 包含隐藏或非导出字段
}
-
wait方法
-
func (c *Cond) Wait()
-
阻塞等待条件变量满足
-
释放已经掌握的互斥锁,相当于
cond.L.Unlock()
,这两步是一个原子操作 -
当被唤醒时,Wait()函数解除阻塞,并重新获得互斥锁。相当于
cond.L.Lock()
-
-
用于唤醒的两个方法
-
func (c *Cond) Signal()
-
func (c *Cond) Broadcast()
-
条件变量与生产者消费者模型
var cond sync.Cond
func consumer(out <-chan int, index int) {
for {
cond.L.Lock()
for len(out) == 0 {
cond.Wait()
}
num := <-out
fmt.Println("第", index, "个消费者消费了", num)
cond.Signal()
cond.L.Unlock()
time.Sleep(time.Second * 1)
}
}
func producer(in chan<- int, index int) {
for {
cond.L.Lock()
for len(in) == cap(in) {
cond.Wait()
}
num := rand.Intn(100)
in <- num
fmt.Println("====第", index, "个生产者生产了", num)
cond.Signal()
cond.L.Unlock()
time.Sleep(time.Second * 1)
}
}
func main() {
rand.Seed(time.Now().UnixNano())
ch := make(chan int, 5)
cond.L = new(sync.Mutex)
for i := 0; i < 5; i++ {
go consumer(ch, i+1)
}
for i := 0; i < 5; i++ {
go producer(ch, i+1)
}
for {
runtime.GC()
}
}
- 注意在使用条件变量的地方要使用循环。因为线程中Wait方法是第一个恢复执行的,而此时c.L未加锁。调用者不应假设Wait恢复时条件已满足,相反,调用者应在循环中等待。
- Signal唤醒等待c的一个线程(如果存在)。调用者在调用本方法时,建议(但并非必须)保持c.L的锁定。