goroutine和channel 如何控制并发顺序?

网友投稿 257 2022-09-28

goroutine和channel 如何控制并发顺序?

文章目录

​​写在前面​​

​​分析​​

​​1. goroutine​​​​2. select​​​​3. sync.WaitGroup​​​​4. channel​​​​5. 代码​​

​​解释​​​​完整​​

写在前面

最近有同学问我这个问题。

题目意思是 利用goroutine和channel 连续输出​​10​​​次,​​dog,cat,fish​​​,并且都要按照这个​​dog,cat,fish​​的顺序输出。

分析

题目既然要求是使用goroutine,那么我们肯定是要控制好这个并发的顺序。因为并发是具有随机性的,这个题目并不难,很典型的chan控制进程之间的顺序。

那我们先了解一下 ​​goroutine ,select ,sync.WaitGroup,channel​​

1. goroutine

我们这里先了解一下go的调度机制,即是GPM模型。goruntine相对线程更加轻量,GPM调度器效率更高。

G:Goroutine 我们所说的协程,为用户级的轻量级线程,每个Goroutine对象中的sched保存着其上下文信息P:Processor 调度,即为G和M的调度对象,用来调度G和M之间的关联关系,其数量可通过​​GOMAXPROCS()​​来设置,默认为核心数M:Machine 真正的工人,对内核级线程的封装,数量对应真实的CPU数

每个Processor对象都拥有一个LRQ(Local Run Queue),未分配的Goroutine对象保存在GRQ(Global Run Queue )中,等待分配给某一个P的LRQ中,每个LRQ里面包含若干个用户创建的Goroutine对象。

同时Processor作为桥梁对Machine和Goroutine进行了解耦,也就是说Goroutine如果想要使用Machine需要绑定一个Processor才行。

2. select

​​select​​ 和 ​​switch​​ 很像,它不需要输入参数,并且仅仅被使用在通道操作上。 select 语句被用来执行多个通道操作的一个和其附带的 case 块代码。

我们知道 select 语句和 switch 很像,不同点是用通道读写操作代替了布尔操作。

通道将被阻塞,除非它有默认的 default 块 (之后将介绍)。一旦某个 case 条件执行,它将不阻塞。

我们发现 select 语句将阻塞,因此 select 将等待,直到有 case 语句不阻塞。

可以使用 select 模拟了一个数百万请求的服务器负载均衡的例子,它从多个有效服务中返回其中一个响应。

使用协程,通道和 ​​select​​ 语句,我们可以向多个服务器请求数据并获取其中最快响应的那个。

3. sync.WaitGroup

​​WaitGroup​​ 是一个带着计数器的结构体,这个计数器可以追踪到有多少协程创建,有多少协程完成了其工作。当计数器为 0 的时候说明所有协程都完成了其工作。

​​Add​​ 方法的参数是一个变量名叫 delta 的int 类型参数,主要用来内部计数。 内部计数器默认值为 0. 它用于记录多少个协程在运行。当 ​​WaitGroup​​ 创建后,计数器值为 0,我们可以通过给 Add方法传 int类型值来增加它的数量。 记住, 当协程建立后,计数器的值不会自动递增 ,因此需要我们手动递增它。​​Wait​​ 方法用来阻塞当前协程。一旦计数器为 0, 协程将恢复运行。 因此,我们需要一个方法去降低计数器的值。​​Done​​ 方法可以降低计数器的值。他不接受任何参数,因此,它每执行一次计数器就减 1。

4. channel

channel 具体看这篇文章吧 ​​channel介绍​​

之前的一篇博客已经讲的很清楚了。

5. 代码

简单了解完上述之后,我们开始写代码。

解释

既然是并发,那么我们就要写3个函数,去分别打印我们的dog,cat,fish了。

这里用dog进行举例

func dog(){ fmt.Println("dog")}

那我们的主函数就要启动goroutine去并发了。大概就是一下这种情况。

func main(){ //...省略一些逻辑 go dog() go cat() go fish() //...省略一些逻辑}

那么我们先控制这三个的并发顺序,可以直接select去阻塞进行调试。

既然要控制并发顺序,我们就要可以用channel进行通信通知。我们先创建三个channel,用chan去传递信息。注意这里是传递无缓冲的channel,因为无缓冲是可以进行读写同步的。用来控制并发顺序最合适不过了。

dogChan, catChan, fishChan := make(chan bool), make(chan bool), make(chan bool)

dogChan 一开始赋值,并且dog打印完之后,给catChan通信,cat打印完之后,给fishChan通信,fish打印完后给dogChan通信。打完10次之后就停止。

比如这个传入dogChan 和 catChan 进行通信。把dogChan的取出,再将catChan的赋值,就可以不断进行循环调度了。

func dog(dogChan chan bool,catChan chan bool ) { for { select { case <-dogChan: fmt.Println("dog") catChan <- true break default: break } }}

我们主程序可以用 sync.WaitGroup 来进行阻塞。当完成10次之后才Done掉,那么就完成了。

func fish(fishChan chan bool,dogChan chan bool ) { i := 0 for { select { case <-fishChan: fmt.Println("fish") i++ if i > 9 { wg.Done() return } dogChan <- true break default: break } }}

完整

package mainimport ( "fmt" "sync")var wg sync.WaitGroupfunc dog(dogChan chan bool,catChan chan bool ) { for { select { case <-dogChan: fmt.Println("dog") catChan <- true break default: break } }}func cat(catChan chan bool,fishChan chan bool ) { for { select { case <-catChan: fmt.Println("cat") fishChan <- true break default: break } }}func fish(fishChan chan bool,dogChan chan bool ) { i := 0 for { select { case <-fishChan: fmt.Println("fish") i++ // 计数,打印完之后就溜溜结束了。 if i > 9 { wg.Done() return } dogChan <- true break default: break } }}func main() { dogChan, catChan, fishChan := make(chan bool), make(chan bool), make(chan bool) wg.Add(1) go dog(dogChan, catChan) go cat(catChan, fishChan) go fish(fishChan, dogChan) dogChan <- true // 记得这里进行启动条件,不然就没法启动了。 wg.Wait()}

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:天翼云:加速推进云网融合 共赢算力时代
下一篇:Java用栈实现综合计算器
相关文章

 发表评论

暂时没有评论,来抢沙发吧~