更好的方法是优雅地停止收听频道

  • 本文关键字:频道 方法 更好 go
  • 更新时间 :
  • 英文 :


我是围棋的学习者。为了模拟Go中的并发性,我写了下面的例子:

package main
import (
"fmt"
"time"
)
func main() {
c1 := make(chan string)
c2 := make(chan string)
go func() {
for i := 0; i < 5; i++ {
time.Sleep(500 * time.Millisecond)
c1 <- "Every 500 ms"
}
close(c1)
}()
go func() {
for i := 0; i < 5; i++ {
time.Sleep(1000 * time.Millisecond)
c2 <- "Every 1 s"
}
close(c2)
}()
isDone := false
for !isDone {
select {
case msg1, ok := <-c1:
if !ok {
isDone = true
break
}
{
fmt.Println(msg1)
}
case msg2, ok := <-c2:
if !ok {
isDone = true
break
}
{
fmt.Println(msg2)
}
}
}
}

正如您所看到的,在主函数的无穷for循环中,我使用变量isDone手动退出循环。但这种方法在我看来很麻烦。难道不可能在select条件下进行信道测距吗?

上述代码的输出是(Go Playground Link(:

Every 500 ms
Every 1 s
Every 500 ms
Every 500 ms
Every 1 s
Every 500 ms
Every 500 ms

这里的问题是:

A";中断";语句终止执行最里面的";对于";,"开关";,或";选择";语句。

因此,在退出select语句后,如果没有额外的检查,我们将无法退出for循环。我已经更新了你的代码,以简化的方式实现这一点:

package main
import (
"fmt"
"time"
)
func main() {
c1 := make(chan string)
c2 := make(chan string)
go func() {
for i := 0; i < 5; i++ {
time.Sleep(500 * time.Millisecond)
c1 <- "Every 500 ms"
}
close(c1)
}()
go func() {
for i := 0; i < 5; i++ {
time.Sleep(1000 * time.Millisecond)
c2 <- "Every 1 s"
}
close(c2)
}()

ok := true
var msg1 string
var msg2 string

for {
select {
case msg1, ok = <-c1:
if !ok {
break
}
{
fmt.Println(msg1)
}
case msg2, ok = <-c2:
if !ok {
break
}
{
fmt.Println(msg2)
}
}
if !ok {
break
}
}
fmt.Println("For loop exited")
}

上面代码的Go操场链接在这里。

我知道这和你写的没有太大区别。可能会稍微简化一些。实现这一点的另一种方法是使用return语句而不是break,并在单独的方法中定义for - select语句(假设可以(。查找以下示例:

package main
import (
"fmt"
"time"
)
func ForSelectMethod(c1 chan string, c2 chan string) {
for {
select {
case msg1, ok := <-c1:
if !ok {
return
}
{
fmt.Println(msg1)
}
case msg2, ok := <-c2:
if !ok {
return
}
{
fmt.Println(msg2)
}
}
}
}
func main() {
c1 := make(chan string)
c2 := make(chan string)
go func() {
for i := 0; i < 5; i++ {
time.Sleep(500 * time.Millisecond)
c1 <- "Every 500 ms"
}
close(c1)
}()
go func() {
for i := 0; i < 5; i++ {
time.Sleep(1000 * time.Millisecond)
c2 <- "Every 1 s"
}
close(c2)
}()
ForSelectMethod(c1, c2)
fmt.Println("For loop exited")
}

转到此处的游乐场链接。

相同的程序,但行数较少。我认为它更可读、更清晰。

package main
import (
"time"
)
func stop(tk ...*time.Ticker) {
for _, t := range tk {
t.Stop()
}
}
func main() {
ta := time.NewTicker(500 * time.Millisecond)
tb := time.NewTicker(1 * time.Second)
cta, ctb := 0, 0
for {
select {
case <-ta.C:
println("Every 500 ms")
cta++
case <-tb.C:
println("Every 1 s")
ctb++
}
if cta == 5 || ctb == 5 {
stop(ta, tb)
break
}
}
}

样本输出:

Every 500 ms
Every 500 ms
Every 1 s
Every 500 ms
Every 500 ms
Every 1 s
Every 500 ms

在这种情况下,常用的方法是使用另一个通道发出终止信号。从该通道读取成为选择中的一种情况。通常,当两个编写goroutine都完成时,您会关闭该通道。没有必要关闭这两个通道。

在您的程序中,当没有任何通道关闭时,处理结束,而另一个通道的内容被丢弃,在某些情况下,实际上会将goroutine写入泄漏到该通道。如果goroutine还没有完成写入,那么它将阻止等待写入一个永远不会读取的通道。

最新更新