【Golang高阶】goroutine、channel、select

一、协程goroutine

①goroutine的基本模型

单进程时代的两个问题?

1、单一执行流程、计算机只能一个任务一个任务处理
2、进程阻塞所带来的的CPU浪费时间

多进程/多线程解决了阻塞问题,那么它是如何工作的,如下图:
在这里插入图片描述
但是多进程/多线程同时也面临着新的问题:

需要大量的切换成本:进程/线程的数量越多,切换成本也越大,也就越浪费。CPU可能60%在执行程序,40%在切换进程中,所以提高CPU的利用率是一个问题。

多线程/多进程带来的问题
那我们如何去处理这个问题,首先看下在操作系统中一个线程的结构是怎么样的,如下图:
在这里插入图片描述在这里插入图片描述

那么我们可以通过这两个空间把线程一分为二,切换成用户线程和内核线程,并且对两者之间进行一个绑定。

在这里插入图片描述
然后分别对他们取个别名,内核线程就叫线程(thread),用户线程就叫协程(co-routine)。

在这里插入图片描述
于是我们可以对这个模型进行一个优化,如下图,协程和线程是一个M:N的关系,这样可以避免进程频繁的切换,也可以避免某一个协程阻塞导致进程阻塞。
在这里插入图片描述

②goroutine的使用

//主goroutine
func main(){
	//创建一个协程去执行newTask()任务
	go  newTask()
	//睡眠五秒后主goroutine退出

	i:=0
	for{
		i++
		if i>5{
			break
		}
		fmt.Printf("main goroutine i=%d\n",i)
		time.Sleep(time.Second)
	}
}

//子goroutine
func newTask(){
	i:=0
	for {//死循环,每隔一秒进行打印
		i++
		fmt.Printf("new goroutine i=%d\n",i)
		time.Sleep(1*time.Second)
	}
}

运行结果:
在这里插入图片描述

通过以上代码我们可以发现:

1.使用关键字go去创建一个子协程
2.main函数是一个主协程,主协程和子协程并发运行
3.一旦主协程退出,子协程立刻退出

协程的退出

//主goroutine
func main(){
	go func(){
		defer fmt.Println("A.defer")
		func(){
			defer fmt.Println("B.defer")
			runtime.Goexit()//退出当前的协程
			fmt.Println("B")
		}()
		fmt.Println("A")
	}()
	for {
		time.Sleep(time.Second)
	}
}

二、管道channel

①goroutine的通信机制

在这里插入图片描述

②基本使用

无缓存的channel

//主goroutine
func main(){
	//定义一个channel管道
	c:=make(chan int)
	go func() {
		defer fmt.Println("子goroutine结束")
		fmt.Println("子goroutine正在运行")
		c<-666//将666发送给c
	}()
	num:=<-c//从c中接收数据,并赋值给管道
	fmt.Println("num=",num)
	fmt.Println("main goroutine结束...")
}

带有缓存的channel

c:=make(chan int,3)//带有缓存的channel
fmt.Println("len(c)=",len(c),"cap(c)=",cap(c))
go func() {
	defer fmt.Println("子协程结束")
	for i:=0;i<4;i++{
		c<-i
		fmt.Println("子协程正在运行,发送的元素=",i,"len(c)=",len(c),"cap(c)=",cap(c))
	}
}()
time.Sleep(2*time.Second)
for i:=0;i<4;i++{
	num:=<-c
	fmt.Println("主协程正在读取num=",num)
}
fmt.Println("mian 结束")

运行结果:

解读:

channel最多可以存储三个数据,如果通道已满,在向里面写数据,就会阻塞。 当channel为空,从里面取数据也会阻塞

channel的关闭

//主goroutine
func main(){
	c:=make(chan int)
	go func(){
		for i:=0;i<5;i++{
			c<-i
		}
		//关闭通道
		close(c)
	}()
	for {
		//ok为true的时候表示channel没有关闭,如果为false表示channel已经关闭
		if data,ok:=<-c;ok{
			fmt.Println(data)
		}else{
			break
		}
	}
	fmt.Println("main finished...")
}

注意:

channel不像文件一样需要经常去关闭,只有当你确实没有任何发送数据了,或者你想显式的结束range循环之类的,才去关闭channel;
关闭channel/后,无法向channel再发送数据(引发panic错误后导致接收立即返回零值)
关闭channel/后,可以继续从channel接收数据; 对于nil channel,无论收发都会被阻塞。

③channel与range

func main(){
	c:=make(chan int)
	go func(){
		for i:=0;i<5;i++{
			c<-i
		}
		close(c)
	}()
	for data:=range c{
		fmt.Println(data)
	}
	fmt.Println("main finished...")
}

④channel与select

单流程下一个go只能监控一个channel的状态,selecti可以完成监控多个channel的状态

//主goroutine
func main(){
	c:=make(chan int)
	quit:=make(chan int)

	go func(){
		for i:=0;i<10;i++{
			fmt.Println(<-c)//从管道c中读取数据
		}
		//如果操作完毕,给quit管道赋值,表示可以退出
		quit<-0
	}()
	fab(c,quit)
}


func fab(c,quit chan int){
	x,y:=1,1
	for {
		select {
		case c<-x://如果管道c可以写入的话,进行赋值
			x=y
			y=x+y
		case <-quit://如果结束管道可读的话,就退出
			fmt.Println("quit")
			return
		}
	}
}

参考资料:

https://www.yuque.com/aceld/mo95lb
https://www.bilibili.com/video/BV1gf4y1r79E?p=27

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

胡桃木子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值