golang 常见坑(1)-select

2019-10-02 10:57:57 +08:00
 guonaihong

这个系列会介绍 golang 常见的坑。当然很多坑是由于对 golang 理解不到位引起的。

猜测下如下代码的输出

这是一段很简单的代码,生产者 go 程打印数字,结束之后发送 cancel 信号。 是不是认为会打印 0-999。如果是这样想的可以继续往下看。

package main

import (
        "context"
        "fmt"
)

func main() {
        ctx, cancel := context.WithCancel(context.Background())

        data := make(chan int, 1000)

        go func() {
                for i := 0; i < 1000; i++ {
                        data <- i
                }
                cancel()
        }()

        for {
                select {
                case <-ctx.Done():
                        return
                case v := <-data:
                        fmt.Printf("%d\n", v)
                }
        }
}

问题分析

你以为会打印 0-999 ?其实不是。。运行下代码你会发现。输出是随机的。what? 这其实和 select 的机制有关系。当 case 条件有多个为真,就想象成随机函数从 case 里面选择一个执行 。上面的代码是两个条件都满足,调用 cancel 函数,有些数据还缓存在 data chan 里面,ctx.Done()条件也为真。选择到 ctx.Done()的时候,这里很可能 case v:=<-data 都没打印全。

解决问题

刚刚聊了 case 的内部逻辑。再聊下如何解决这个问题。data 每个发送的数据都确保消费掉,最后再调用 cancel 函数就可解决这个问题。做法把带缓冲的 chan 修改为不带缓冲。

// data := make(chan int, 1000)
data := make(chan int)

最佳实践

如果不是必须的理由要用带缓冲的 chan。推荐使用无缓冲的 chan。至于担心的性能问题,他们性能差距不大。后面会补上 benchmark。

我的 github

https://github.com/guonaihong/gout

5727 次点击
所在节点    Go 编程语言
24 条回复
guonaihong
2019-10-02 21:51:37 +08:00
@heimeil 如果 chan 是带缓冲的,并且因为某些原因不能修改为无缓冲的,可以用下面的该法。你的代码我看了,用两层 for 循环的做法,本质还是想知道 chan 有没有空。直接用个判断就行。

```go
ackage main

import (
"context"
"fmt"
"time"
)

func main() {
ctx, cancel := context.WithCancel(context.Background())

data := make(chan int, 10)

go func() {
for i := 0; i < 10; i++ {
data <- i
}
cancel()
fmt.Println("cancel")
}()

for {
select {
case <-ctx.Done():
if len(data) == 0 {
fmt.Println("Done")
return
}
case v := <-data:
fmt.Printf("v = %d\n", v)
}
}
}

```
heimeil
2019-10-02 22:40:32 +08:00
并不是判断为空的意思,你可以这样试试看:
case <-ctx.Done():
if len(data) == 0 {
fmt.Println("Done")
return
} else {
fmt.Println("--------------")
}
znood
2019-10-03 16:45:50 +08:00
肯定要用带缓冲的,不带缓冲的两遍阻塞用两个协程没有意义,用一个协程就处理了
codehz
2019-10-03 17:41:47 +08:00
@znood #23 不不不,有意义,chan 的重点是对程序逻辑的拆解(或者说通过加一层抽象解决复杂问题),而且很多时候并非性能热点,chan 的阻塞操作性能还比有缓冲的高不少)虽然肯定没直接一个的快)
就像很多时候明明可以复制粘贴,为啥要写一个函数呢,这里 chan 的作用就在于此,在合适的地方拆分模块,复用代码,降低耦合性,并不是所有场景都能用回调解决

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/605974

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX