goroutine使用channel阻塞执行时存在输出缺失的情况

Go初学者,在学习 @无闻 大神的 《Go编程基础(视频)》 的第14课并发时,对其中的示例代码有疑问,本地执行的输出结果不符合预期。

运行环境

$ go version
go version go1.17.2 darwin/arm64

问题描述

预期应该是阻塞执行,按顺序0-4输出全部结果,实际多次执行输出时中间会存在缺失的情况

func main() {
    num1 := runtime.GOMAXPROCS(1)
    num2 := runtime.GOMAXPROCS(1)
    fmt.Println("num1:", num1, "num2:", num2)
    c := make(chan bool)
    for j := 0; j < 5; j++ {
        go Go(c, j)
    }
    <-c
}

func Go(c chan bool, index int) {
    a := 1
    for i := 0; i < 100000000; i++ {
        a += i
    }
    fmt.Println(index, a)
    if index == 4 {
        c <- true
    }
}

实际输出结果

goroutine使用channel阻塞执行时存在缺失的情况

通过多方谷歌和百多搜索没有找到原因,望大家能帮忙解答一下 :cry:

最佳答案

根据给出的代码片段,如果第一次执行的是 index=4 的 goroutine,main 这个主 goroutine 则会执行完毕,剩下的子 goroutine 则会被丢弃,所以这一步需要让主 goroutine 阻塞,可以使用 sync.WaitGroup。

如果没有加同步控制,goroutine的执行顺序是无法预测。想要实现顺序输出的话,可以使用 sync/atomic 进行原子操作,每执行到指定的 index 时原子递增,否则等待。

案例:

var wg sync.WaitGroup
var count uint32

func main() {
    num1 := runtime.GOMAXPROCS(1)
    num2 := runtime.GOMAXPROCS(1)
    fmt.Println("num1:", num1, "num2:", num2)
    for j := uint32(0); j < 5; j++ {
        wg.Add(1)
        go Go(j)
    }
    wg.Wait()
}

func Trigger(i uint32, fn func())  {
    for {
        if n := atomic.LoadUint32(&count); n == i {
            fn()
            atomic.AddUint32(&count, 1)
            break
        }
        time.Sleep(time.Nanosecond)
    }
}

func Go(index uint32) {
    Trigger(index, func() {
        a := 1
        for i := 0; i < 100000000; i++ {
            a += i
        }
        fmt.Println(index, a)
    })
    wg.Done()
}

案例输出:

num1: 6 num2: 1
0 4999999950000001
1 4999999950000001
2 4999999950000001
3 4999999950000001
4 4999999950000001
3年前 评论
讨论数量: 4

我理解各个goroutine不是顺序执行的,所以假如index == 4提前向channel发送true的话,主进程结束,其余的协程就不会继续打印了,在main函数的c <- 后sleep一下,就可以打印出无顺序的5个值

Go

3年前 评论
i-am-king (楼主) 3年前

main() 退出的时候,fmt.Println 还没打印完。可以使用 sync.WaitGroup 让主线程等待 goroutine 运行完。 代码改成下面的试试


func main() {
    num1 := runtime.GOMAXPROCS(1)
    num2 := runtime.GOMAXPROCS(1)
    fmt.Println("num1:", num1, "num2:", num2)
    c := make(chan bool)
    var wg sync.WaitGroup
    wg.Add(5)
    for j := 0; j < 5; j++ {
        go Go(c, j, &wg)
    }
    <-c
    close(c)
    wg.Wait()
}

func Go(c chan bool, index int, wg *sync.WaitGroup) {
    a := 1
    for i := 0; i < 100000000; i++ {
        a += i
    }
    fmt.Println(index, a)
    if index == 4 {
        c <- true
    }
    wg.Done()
}
3年前 评论

根据给出的代码片段,如果第一次执行的是 index=4 的 goroutine,main 这个主 goroutine 则会执行完毕,剩下的子 goroutine 则会被丢弃,所以这一步需要让主 goroutine 阻塞,可以使用 sync.WaitGroup。

如果没有加同步控制,goroutine的执行顺序是无法预测。想要实现顺序输出的话,可以使用 sync/atomic 进行原子操作,每执行到指定的 index 时原子递增,否则等待。

案例:

var wg sync.WaitGroup
var count uint32

func main() {
    num1 := runtime.GOMAXPROCS(1)
    num2 := runtime.GOMAXPROCS(1)
    fmt.Println("num1:", num1, "num2:", num2)
    for j := uint32(0); j < 5; j++ {
        wg.Add(1)
        go Go(j)
    }
    wg.Wait()
}

func Trigger(i uint32, fn func())  {
    for {
        if n := atomic.LoadUint32(&count); n == i {
            fn()
            atomic.AddUint32(&count, 1)
            break
        }
        time.Sleep(time.Nanosecond)
    }
}

func Go(index uint32) {
    Trigger(index, func() {
        a := 1
        for i := 0; i < 100000000; i++ {
            a += i
        }
        fmt.Println(index, a)
    })
    wg.Done()
}

案例输出:

num1: 6 num2: 1
0 4999999950000001
1 4999999950000001
2 4999999950000001
3 4999999950000001
4 4999999950000001
3年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!