Golang Go语言中求解释一个并发 Chan 的问题

发布于 1周前 作者 yuanlaile 来自 Go语言

Golang Go语言中求解释一个并发 Chan 的问题

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 &lt;-time.After(10 * time.Millisecond):
case gotChan = &lt;-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)

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


更多关于Golang Go语言中求解释一个并发 Chan 的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html

18 回复

2

更多关于Golang Go语言中求解释一个并发 Chan 的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


go 也是只有 happens before 的吧,不能依靠其他假设

runtime.GOMAXPROCS(1) 你就可以能得到想要的结果了,🐶

并发本来就没有先后,有先后叫啥并发,队列才有先后

我猜测是 timer 性能问题,我记得 time.After 底层超时前都不会被 GC 回收,我这台电脑定时器精度只有 1ms

精度高了才会有上边的问题吧,你在自己电脑上跑一下看看是不是都是 true

不行的啊,我试了一下仍然出现很多 false

问题只是缓解了,并没消除,退一步说这也不是解决问题的办法

感觉你这是在堵 timer 和 sleep 啊,和 chan 没关系吧

golang<br>select {<br> //10 毫秒超时<br> case &lt;-time.After(500 * time.Microsecond):<br> fmt.Println("im here") // &lt;---- 问题在这里<br> case gotChan = &lt;-someChan:<br> }<br>

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

如果你只是想要个超时的话,一般用 context 吧,比如这样
golang<br><br>func ThreadWithContext(ctx context.Context) bool {<br> someChan := make(chan bool)<br> go SomeFunc(someChan)<br> select {<br> case &lt;-ctx.Done():<br> return false<br> case x := &lt;-someChan:<br> return x<br> }<br>}<br>

你这个并不能解决返回 false 的问题啊,我现在是想搞清楚为什么返回 false 的问题,context 的 withTimeout 和 withDeadline 在这里并不影响问题的逻辑。

返回 false 是因为 case &lt;-time.After(10 * time.Millisecond): ,return 了 gotChan 的默认值 false . goroutine 的调度是不能保证的,说白了你想要的代码还没调度执行到 就超时了(10 * time.Millisecond)

不清楚你这个代码要实现什么目的,还以为是个简单的超时限制。不知道想做什么怎么用,那么也不知道怎么改,如果只是要 100%返回 true, 那么办法多的是。

我这个程序是对一个问题的抽象,可能抽象过度了,我说下原来的场景。

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

这是我们使用的一个框架的底层源代码,我们发现了这个问题,并想着手解决,这就是前因后果,抱歉让你误解了。

这里的超时确实是使用 Context 包更好,但是框架的设计者不是这么做的,我们旨在解决请求未超时但是进入了 Timeout 的情况。我认为之所以出现了 false,是因为 SomeFunc 调度的时候,gotChan 还没有进入到读状态,这时候向 someChan 写入,会进入 default 逻辑,进而导致产生了超时。

这个超时问题关键在向 chan 写入的时候,读还未就绪,从而进入了 select 中的 default 选项;当读就绪的时候,chan 已经写入过了,从而阻塞在读取状态,直至超时,跟 Sleep 没太大关系啊。

那么是否应该考虑读者没准备好时写的阻塞呢?写者进到 default 消息就直接丢了

所以我提出的解决方案是缓冲区或者写的时候设置个超时

在Golang(Go语言)中,并发编程是一个非常重要的特性,而Channel(Chan)则是Go语言中实现并发的一种关键机制。下面是对并发中Channel的一个简要解释:

  1. Channel的基本概念: Channel是Go语言中用于在不同goroutine(Go的并发体)之间进行通信的一种类型化管道。你可以发送和接收特定类型的值到Channel中,而不需要使用显式的锁或其他同步机制。

  2. 并发中的Channel: 在并发编程中,Channel用于协调多个goroutine的执行。你可以使用Channel来传递数据、同步操作或实现其他复杂的并发模式。

  3. Channel的操作

    • 发送:使用 chan <- 值 的语法将一个值发送到Channel中。
    • 接收:使用 <- chan 的语法从Channel中接收一个值。接收操作会阻塞,直到有值可用。
  4. Buffered和Unbuffered Channel

    • Buffered Channel有固定的容量,可以在没有接收者的情况下存储多个值。
    • Unbuffered Channel没有容量,发送操作会阻塞直到有接收者准备好接收值。
  5. 关闭Channel: 使用 close(chan) 来关闭一个Channel,表示不会再有值发送到Channel中。接收者可以通过检测语法 v, ok := <-chan 来判断Channel是否已关闭。

总之,Channel是Go语言中并发编程的核心,它提供了一种简洁而强大的方式来在goroutine之间传递数据和同步操作。理解Channel的使用是掌握Go语言并发编程的关键。

回到顶部