打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

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

文章目录

写在前面

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

题目意思是 利用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

selectswitch 很像,它不需要输入参数,并且仅仅被使用在通道操作上
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 main

import (
"fmt"
"sync"
)

var wg sync.WaitGroup

func 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()
}
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Go 最细节篇 — chan 为啥没有判断 close 的接口 ?
Go 语法速览与实践清单(V0.5)
多图详解Go中的Channel源码
Goroutine + Channel 实践 | Golang中文社区(Go语言构建) | Go语言中文网 | Go语言学习园地
Go语言_并发篇
深入NSQ 之旅
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服