go语言提升(三):select、死锁、互斥锁、读写锁、条件变量、生产者消费者模型

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实现超时处理

步骤:

  1. 创建select,启动监听channel

    case num := <-ch:

  2. 监听超时计时器:当其他分支满足时,该计时器会被时间重置

    case <-time.After(time.Second*3):

  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. 死锁

产生死锁的几种情况:

  1. 一个go程自己读channel又写channel
  2. 多个go程使用channel通信,在go程创建之前对channel读或者写
  3. 多个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的锁定。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值