为了账号安全,请及时绑定邮箱和手机立即绑定

带有活动 goroutine 数量的奇怪输出

带有活动 goroutine 数量的奇怪输出

Go
绝地无双 2022-06-27 16:22:07
我试图通过运行小程序来学习频道。我不明白下面的程序,因为它给出了一个奇怪的输出:func squares(c chan int) {    for i := 0; i < 4; i++ {        num := <-c        fmt.Println(num * num)    }}func main() {    c := make(chan int, 3)    go squares(c)    c <- 1    c <- 2    c <- 3    c <- 4    fmt.Println(runtime.NumGoroutine())    time.Sleep(time.Second)    fmt.Println(runtime.NumGoroutine())}在执行这个程序时,我看到第一次它打印活动 goroutine 的数量为 2。然后一秒钟后,它的输出为 1。这真的很奇怪。看了几篇博客,没看懂。那么,在 goroutine 停止工作的那一秒内,究竟发生了什么变化?
查看完整描述

3 回答

?
慕的地10843

TA贡献1785条经验 获得超8个赞

无法保证 goroutine 的顺序。根据您的观察,看起来在写入通道后,主 goroutine 继续并将活动的 goroutine 打印为两个,然后squaresgoroutine 运行并完成。

您也有可能从第一次调用中得到 1 来获取 goroutine 的数量。如果squaresgoroutine 在主 goroutine 写入通道后立即运行,就会发生这种情况。您可以通过在写入通道之后但在获取 goroutine 数量之前添加睡眠调用来强制执行此操作。


查看完整回答
反对 回复 2022-06-27
?
潇潇雨雨

TA贡献1833条经验 获得超4个赞

您的第二个 goroutine 接收 4 个值,打印它们并squares几乎立即退出(返回时)。你很幸运,它在第一次调用时仍在运行NumGoRoutines

顺便说一句,我建议您阅读有关该语言的信息,而不是通过实验来学习。仅仅因为某些东西在您尝试时有效,并不意味着它是正确的方法并且总是有效的,尤其是在涉及并发的情况下。或者,使用竞争检测器显示任何数据竞争。


查看完整回答
反对 回复 2022-06-27
?
jeck猫

TA贡献1909条经验 获得超7个赞

让我强制执行排序,这样你甚至不需要计算 goroutine 的数量,试试这个:

package main


import (

    "fmt"

    "runtime"

    "sync"

)


func main() {

    var wg sync.WaitGroup

    ch := make(chan int)

    wg.Add(1)

    go func() {

        defer wg.Done()

        for n := range ch {

            fmt.Println(n)

        }

    }()


    ch <- 1

    ch <- 2

    ch <- 3

    ch <- 4


    fmt.Println("NumGoroutine =", runtime.NumGoroutine()) // 2

    close(ch)

    wg.Wait()

    fmt.Println("NumGoroutine =", runtime.NumGoroutine()) // 1

}


输出:


1

2

3

NumGoroutine = 2

4

NumGoroutine = 1

笔记:


使用无缓冲通道强制写入通道和从通道读取的一对一同步。

显式关闭通道以退出for循环。

显式使用wg.Done()表示 goroutine 的结束,所以wg.Wait()等待它。

让我先展开循环for:

func squares(c chan int) {

    num := <-c

    fmt.Println(num * num)

    num = <-c

    fmt.Println(num * num)

    num = <-c

    fmt.Println(num * num)

    num = <-c

    fmt.Println(num * num)

}

func main() {

    c := make(chan int, 3)

    go squares(c)

    c <- 1

    c <- 2

    c <- 3

    c <- 4

    fmt.Println("NumGoroutine =", runtime.NumGoroutine())

    time.Sleep(100 * time.Millisecond)

    fmt.Println("NumGoroutine =", runtime.NumGoroutine())

}


现在让我们运行它:

由于您使用的是 3 的缓冲通道,因此这 3 行运行速度很快(这意味着无需等待通道同步 - 只需附加1, 2, 3到通道的缓冲区):


    c <- 1

    c <- 2

    c <- 3

并且根据操作系统和 CPU 负载,消费者 goroutine 可能会全部消耗或不消耗它们,

此时c <- 4,我们有两种情况:


通道已满:所以maingoroutine 等待通道同步。

通道有一个(或多个)空闲位置:因此c <- 4运行速度很快,只是将4通道的缓冲区放入(无需等待通道同步)。

现在,在这c <- 4一点之后,我们也有两种情况:


另一个 goroutine 使用4并退出,所以我们只有一个 maingoroutine。

另一个 goroutine不消耗4,所以我们有两个goroutine。


查看完整回答
反对 回复 2022-06-27
  • 3 回答
  • 0 关注
  • 115 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号