在 Go 中收听多个频道
欢迎回到系列!今天,我们将研究同时收听多个频道的方法。之前的指南帮助您开始使用 Go 中的并发性。尽管简单的方法通常是最好的方法,但您可能一直在尝试实现更复杂的行为。阅读本指南后,您将能够使您的并发代码更加灵活。
选择关键字
我们可以使用 选择
关键字一次收听多个 goroutine。
包主 进口 (
“fmt”
“时间”
) 功能主要(){
c1 := make(chan 字符串)
c2 := make(chan 字符串) 去函数(){
time.Sleep(1 * time.Second)
c1 <- time.Now().String()
}() 去函数(){
time.Sleep(2 * time.Second)
c2 <- time.Now().String()
}() 对于我:= 0;我 < 2;我++ {
选择 {
案例 res1 := <-c1:
fmt.Println("来自 c1:", res1)
案例 res2 := <-c2:
fmt.Println("来自 c2:", res2)
}
}
} 来自 c1:2022-09-04 14:30:39.4469184 -0400 EDT m=+1.000172801
来自 c2:2022-09-04 14:30:40.4472699 -0400 EDT m=+2.000524401
上面的代码显示了如何 选择
关键字有效。
- 我们首先创建两个通道
c1
和c2
听。 - 然后我们生成两个 goroutine,每个都将当前时间发送到
c1
和c2
. - 在 for 循环中,我们创建一个
选择
声明并定义两种情况:第一种情况是我们何时可以收到来自c1
第二个是我们什么时候可以收到c2
.
你可以看到 选择
声明在设计上与 转变
陈述。两者都定义了不同的情况,并在满足特定情况时运行各自的代码。此外,我们可以看到 选择
语句被阻塞。也就是说,它将等到满足其中一种情况。
我们为循环迭代了两次,因为只有两个 goroutine 需要监听。更准确地说,每个 goroutine 都是一个即发即弃的 goroutine,这意味着它们在返回之前只向一个通道发送一次。因此,这段代码中始终最多有两条消息,我们只需要选择两次。
如果我们不知道工作何时结束怎么办?
有时我们不知道有多少工作。在这种情况下,将 选择
while 循环中的语句。
包主 进口 (
“fmt”
“数学/兰特”
“时间”
) 功能主要(){
c1 := make(chan 字符串)
rand.Seed(time.Now().UnixNano()) 对于我:= 0;我 < rand.Intn(10);我++ {
去函数(){
time.Sleep(1 * time.Second)
c1 <- time.Now().String()
}()
} 为了 {
选择 {
案例 res1 := <-c1:
fmt.Println("来自 c1:", res1)
}
}
}
因为我们让随机数量的 goroutine 运行,所以我们不知道有多少作业。值得庆幸的是,底部包含 select 语句的 for 循环将捕获每个输出。让我们看看如果我们运行这段代码会发生什么。
从 c1: 2022-09-04 14:48:47.5145341 -0400 EDT m=+1.000257801
从 c1: 2022-09-04 14:48:47.5146126 -0400 EDT m=+1.000336201
从 c1: 2022-09-04 14:48:47.5146364 -0400 EDT m=+1.000359901
致命错误:所有 goroutine 都处于休眠状态 - 死锁! goroutine 1 [chan 接收]:
main.main()
/home/jacob/blog/testing/listening-to-multiple-channels-in-go/main.go:22 +0x128
退出状态 2
嗯,select语句按预期接收了3次,但是程序因为死锁而报错了。为什么会这样?
请记住,在没有任何条件的情况下,Go 中的 for 循环将永远运行。这意味着 select 语句将尝试永远接收。但是,要运行的作业数量是有限的。即使没有更多作业,select 语句仍会尝试接收。
还记得在本系列的第一篇文章中我说过,如果您在发送方未准备好时尝试从无缓冲通道接收,您的程序将陷入死锁吗?这正是我们示例中的情况。
那么我们如何解决这个问题呢?我们可以结合使用之前文章中介绍的概念:退出通道和 WaitGroups。
包主 进口 (
“fmt”
“数学/兰特”
“同步”
“时间”
) 功能主要(){
c1 := make(chan 字符串)
退出 := make(chan struct{})
rand.Seed(time.Now().UnixNano())
var wg sync.WaitGroup 去函数(){
numJob := rand.Intn(10)
fmt.Println("作业数:", numJob)
对于我:= 0;我<numJob;我++ {
wg.Add(1)
去函数(){
推迟 wg.Done()
time.Sleep(1 * time.Second)
c1 <- time.Now().String()
}()
}
wg.Wait()
关闭(退出)
}() 为了 {
选择 {
案例 res1 := <-c1:
fmt.Println("来自 c1:", res1)
案例<-退出:
返回
}
}
} 3
从 c1: 2022-09-04 15:09:08.6936976 -0400 EDT m=+1.000287801
从 c1: 2022-09-04 15:09:08.6937788 -0400 EDT m=+1.000369101
从 c1: 2022-09-04 15:09:08.6937949 -0400 EDT m=+1.000385101
- 我们创建了一个退出通道和一个 WaitGroup。
- 每次运行的作业数量是随机的。为了
numJobs
很多次,我们会触发 goroutines。为了等待作业完成,我们将它们添加到工作组
.当一项工作完成后,我们从工作组
. - 完成所有作业后,我们关闭退出通道。
- 我们将上面的部分包装在一个 goroutine 中,因为我们希望所有这些都是非阻塞的。如果我们不将它包装在 goroutine 中,
wg.Wait()
将等到工作完成。这将阻止代码,并且不会让底部的 for-select 语句运行。 - 此外,由于
c1
是一个无缓冲的通道,等待所有的 goroutine 发送消息到c1
将导致许多消息被发送到c1
没有 for-select 语句来接收它们。这会导致死锁,因为当发送者准备好时,接收者还没有准备好。
如何使选择非阻塞
这 选择
语句默认是阻塞的。我们如何使这个非阻塞?这很简单——我们只是添加一个默认情况。
包主 进口 (
“fmt”
“数学/兰特”
“同步”
“时间”
) 功能主要(){
ashleyMsg := make(chan 字符串)
brianMsg := make(chan 字符串)
退出 := make(chan struct{})
rand.Seed(time.Now().UnixNano())
var wg sync.WaitGroup 去函数(){
numJob := rand.Intn(10)
fmt.Println("作业数:", numJob)
对于我:= 0;我<numJob;我++ {
wg.Add(2)
去函数(){
推迟 wg.Done()
time.Sleep(time.Duration(rand.Intn(10)) * time.Millisecond)
ashleyMsg <-“嗨”
}()
去函数(){
推迟 wg.Done()
time.Sleep(time.Duration(rand.Intn(10)) * time.Millisecond)
brianMsg <-“怎么了”
}()
}
wg.Wait()
关闭(退出)
}() 为了 {
选择 {
案例 res1 := <-ashleyMsg:
fmt.Println("阿什利:", res1)
案例 res2 := <-brianMsg:
fmt.Println("布莱恩:", res2)
案例<-退出:
fmt.Println("聊天结束")
返回
默认:
fmt.Println("...")
时间.睡眠(时间.毫秒)
}
}
} ...
工作数量:4
布莱恩:怎么了
...
阿什利:嗨
...
...
布莱恩:怎么了
阿什利:嗨
阿什利:嗨
布莱恩:怎么了
...
...
阿什利:嗨
...
布莱恩:怎么了
...
聊天结束
除了蹩脚的对话之外,我们还可以看到默认案例是如何工作的。我们可以在没有可接收的渠道时做一些事情,而不是等待聊天到达。在这个例子中,我们只是打印出椭圆,但你可以做任何你想做的事情。
结论
这就是这篇文章!现在您可以同时收听多个频道,这在您开发个人项目时可能是一笔巨大的财富。感谢阅读,我们下期再见。
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明
本文链接:https://www.qanswer.top/18514/55550608
标签:语句,频道,chan,make,goroutine,收听,Go,c1,我们 From: https://www.cnblogs.com/amboke/p/16660444.html