求解释一个 Golang 并发 Chan 的问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
phpcyy
V2EX    Go 编程语言

求解释一个 Golang 并发 Chan 的问题

  •  
  •   phpcyy 2020-08-24 20:03:21 +08:00 2336 次点击
    这是一个创建于 1875 天前的主题,其中的信息可能已经有所发展或是发生改变。
    func Test_TarsSelect(t *testing.T) { trueCount := 0 falseCount := 0 for i := 0; i < 100000; i++ { if TarsThread() { trueCount++ } else { falseCount++ } } fmt.Println("trueCount:", trueCount) fmt.Println("falseCount:", falseCount) } func TarsThread() bool { someChan := make(chan bool) var gotChan bool go SomeFunc(someChan) select { //10 毫秒超时 case <-time.After(10 * time.Millisecond): case gotChan = <-someChan: } return gotChan } func SomeFunc(someChan chan bool) { //休眠 10 微秒 time.Sleep(10 * time.Microsecond) select { case someChan <- true: default: } } 

    如上所示的代码,在我这边执行 Test_TarsSelect 的结果是

    === RUN Test_TarsSelect trueCount: 99961 falseCount: 39 

    一般情况下会认为应该全部为 true

    如何解释这种现象呢? golang 中所有并发的程序的先后性都是不保证的吗?

    我认为解决办法是 someChan 初始化时,可以修改为缓冲区长度为 1 。
    另外一个同事认为是将 SomeFunc 中的 default 逻辑去掉,改成 case <- time.After(time.MillionSecond * 10)

    二者都可以解决问题,你们推荐使用哪一种,或者有更合理的解决办法吗?

    17 条回复    2020-08-25 19:32:11 +08:00
    securityCoding
        1
    securityCoding  
       2020-08-24 20:08:15 +08:00
    2
    lance6716
        2
    lance6716  
       2020-08-24 20:27:59 +08:00 via Android   1
    go 也是只有 happens before 的吧,不能依靠其他假设
    whimsySun
        3
    whimsySun  
    2020-08-24 21:30:13 +08:00
    runtime.GOMAXPROCS(1) 你就可以能得到想要的结果了,
    sujin190
        4
    sujin190  
       2020-08-24 23:01:49 +08:00
    并发本来就没有先后,有先后叫啥并发,队列才有先后
    goofool
        5
    goofool  
       2020-08-25 10:25:59 +08:00
    我猜测是 timer 性能问题,我记得 time.After 底层超时前都不会被 GC 回收,我这台电脑定时器精度只有 1ms
    phpcyy
        6
    phpcyy  
    OP
       2020-08-25 10:44:59 +08:00
    @goofool 精度高了才会有上边的问题吧,你在自己电脑上跑一下看看是不是都是 true
    phpcyy
        7
    phpcyy  
    OP
       2020-08-25 11:02:04 +08:00
    @whimsySun 不行的啊,我试了一下仍然出现很多 false
    phpcyy
        8
    phpcyy  
    OP
       2020-08-25 11:07:26 +08:00
    @whimsySun 问题只是缓解了,并没消除,退一步说这也不是解决问题的办法
    yianing
        9
    yianing  
       2020-08-25 13:16:34 +08:00 via Android
    感觉你这是在堵 timer 和 sleep 啊,和 chan 没关系吧
    sunxiansong
        10
    sunxiansong  
       2020-08-25 14:20:29 +08:00
    ```golang
    select {
    //10 毫秒超时
    case <-time.After(500 * time.Microsecond):
    fmt.Println("im here") // <---- 问题在这里
    case gotChan = <-someChan:
    }
    ```

    楼主你大概没发现问题在哪,所以实际上你的 2 个办法都不能解决问题。

    如果你只是想要个超时的话,一般用 context 吧,比如这样
    ```golang

    func ThreadWithContext(ctx context.Context) bool {
    someChan := make(chan bool)
    go SomeFunc(someChan)
    select {
    case <-ctx.Done():
    return false
    case x := <-smeChan:
    return x
    }
    }
    ```
    phpcyy
        11
    phpcyy  
    OP
       2020-08-25 15:55:39 +08:00
    @sunxiansong 你这个并不能解决返回 false 的问题啊,我现在是想搞清楚为什么返回 false 的问题,context 的 withTimeout 和 withDeadline 在这里并不影响问题的逻辑。
    sunxiansong
        12
    sunxiansong  
       2020-08-25 16:07:57 +08:00
    @phpcyy 返回 false 是因为 `case <-time.After(10 * time.Millisecond):` ,return 了 gotChan 的默认值 false . goroutine 的调度是不能保证的,说白了你想要的代码还没调度执行到 就超时了(10 * time.Millisecond)

    不清楚你这个代码要实现什么目的,还以为是个简单的超时限制。不知道想做什么怎么用,那么也不知道怎么改,如果只是要 100%返回 true, 那么办法多的是。
    phpcyy
        13
    phpcyy  
    OP
       2020-08-25 18:12:49 +08:00
    @sunxiansong 我这个程序是对一个问题的抽象,可能抽象过度了,我说下原来的场景。

    这个程序原来是在 `go SomeFunc(someChan)` 这一句发送了一个 tcp 请求,然后主程序进入等待响应阶段; SomeFunc 这个 goroutine 会执行 tcp 请求,并将结果写入 someChan 。SomeFunc 里的 `time.Sleep(10 * time.MicroSecond)` 是模拟的请求过程的请求时间。

    这是我们使用的一个框架的底层源代码,我们发现了这个问题,并想着手解决,这就是前因后果,抱歉让你误解了。
    phpcyy
        14
    phpcyy  
    OP
       2020-08-25 18:20:41 +08:00
    @sunxiansong 这里的超时确实是使用 Context 包更好,但是框架的设计者不是这么做的,我们旨在解决请求未超时但是进入了 Timeout 的情况。我认为之所以出现了 false,是因为 SomeFunc 调度的时候,gotChan 还没有进入到读状态,这时候向 someChan 写入,会进入 default 逻辑,进而导致产生了超时。
    phpcyy
        15
    phpcyy  
    OP
       2020-08-25 18:25:05 +08:00
    @yianing 这个超时问题关键在向 chan 写入的时候,读还未就绪,从而进入了 select 中的 default 选项;当读就绪的时候,chan 已经写入过了,从而阻塞在读取状态,直至超时,跟 Sleep 没太大关系啊。
    yianing
        16
    yianing  
       2020-08-25 19:30:37 +08:00 via Android
    @phpcyy 那么是否应该考虑读者没准备好时写的阻塞呢?写者进到 default 消息就直接丢了
    phpcyy
        17
    phpcyy  
    OP
       2020-08-25 19:32:11 +08:00 via iPhone
    @yianing 所以我提出的解决方案是缓冲区或者写的时候设置个超时
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     3528 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 25ms UTC 04:36 PVG 12:36 LAX 21:36 JFK 00:36
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86