c语言sscanf函数的用法是什么
257
2022-09-28
goroutine和channel 如何控制并发顺序?
文章目录
写在前面
分析
1. goroutine2. select3. sync.WaitGroup4. channel5. 代码
解释完整
写在前面
最近有同学问我这个问题。
题目意思是 利用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小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~