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)
。
二者都可以解决问题,你们推荐使用哪一种,或者有更合理的解决办法吗?
1 securityCoding 2020-08-24 20:08:15 +08:00 2 |
![]() | 2 lance6716 2020-08-24 20:27:59 +08:00 via Android ![]() go 也是只有 happens before 的吧,不能依靠其他假设 |
![]() | 3 whimsySun 2020-08-24 21:30:13 +08:00 runtime.GOMAXPROCS(1) 你就可以能得到想要的结果了, |
![]() | 4 sujin190 2020-08-24 23:01:49 +08:00 并发本来就没有先后,有先后叫啥并发,队列才有先后 |
![]() | 5 goofool 2020-08-25 10:25:59 +08:00 我猜测是 timer 性能问题,我记得 time.After 底层超时前都不会被 GC 回收,我这台电脑定时器精度只有 1ms |
![]() | 9 yianing 2020-08-25 13:16:34 +08:00 via Android 感觉你这是在堵 timer 和 sleep 啊,和 chan 没关系吧 |
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 } } ``` |
![]() | 11 phpcyy OP @sunxiansong 你这个并不能解决返回 false 的问题啊,我现在是想搞清楚为什么返回 false 的问题,context 的 withTimeout 和 withDeadline 在这里并不影响问题的逻辑。 |
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, 那么办法多的是。 |
![]() | 13 phpcyy OP @sunxiansong 我这个程序是对一个问题的抽象,可能抽象过度了,我说下原来的场景。 这个程序原来是在 `go SomeFunc(someChan)` 这一句发送了一个 tcp 请求,然后主程序进入等待响应阶段; SomeFunc 这个 goroutine 会执行 tcp 请求,并将结果写入 someChan 。SomeFunc 里的 `time.Sleep(10 * time.MicroSecond)` 是模拟的请求过程的请求时间。 这是我们使用的一个框架的底层源代码,我们发现了这个问题,并想着手解决,这就是前因后果,抱歉让你误解了。 |
![]() | 14 phpcyy OP @sunxiansong 这里的超时确实是使用 Context 包更好,但是框架的设计者不是这么做的,我们旨在解决请求未超时但是进入了 Timeout 的情况。我认为之所以出现了 false,是因为 SomeFunc 调度的时候,gotChan 还没有进入到读状态,这时候向 someChan 写入,会进入 default 逻辑,进而导致产生了超时。 |