dpqaaczn141761 2019-03-14 14:13
浏览 75

从goroutine中的缓冲通道读取时的执行顺序

I was playing around with concurrency and buffered channels and I hit upon the following problem that left me confused: https://play.golang.org/p/wir7wP2u-yf

  1. Why exactly does the 'unload' of the channel (of size 3) inside the function echo happen with the 4 included?

  2. Why does the capacity of the channel c stay 0 after 5 is sent to the channel?

  3. Why is 10 not echoed?

package main

import "fmt"

func echo(c chan int) {
    for num := range c {
        //fmt.Printf("length of channel c: %v
",len(c))
        fmt.Println(num)
    }
    fmt.Println("Done iterating")
}

func main() {
    fmt.Println("main() started")
    c := make(chan int, 3)

    go echo(c)

    c <- 1
    fmt.Printf("After 1, capacity %v
",len(c))
    c <- 2
    fmt.Printf("After 2, capacity %v
",len(c))
    c <- 3
    fmt.Printf("After 3, capacity %v
",len(c))
    c <- 4 // blocks here
    fmt.Printf("After 4, capacity %v
",len(c))
    c <- 5
    fmt.Printf("After 5, capacity %v
",len(c))
    c <- 6
    fmt.Printf("After 6, capacity %v
",len(c))
    c <- 7
    fmt.Printf("After 7, capacity %v
",len(c))
    c <- 8
    fmt.Printf("After 8, capacity %v
",len(c))
    c <- 9
    fmt.Printf("After 9, capacity %v
",len(c))
    c <- 10
    fmt.Printf("After 10
")
    fmt.Println("main() stopped")
}
  • 写回答

1条回答 默认 最新

  • duanji1899 2019-03-14 14:58
    关注

    It entirely depends on OS scheduling. The results of the above code would not be always the same.

    main() started
    After 1, capacity 1
    After 2, capacity 2
    After 3, capacity 3
    1
    2
    3
    4
    After 4, capacity 0
    After 5, capacity 0
    After 6, capacity 1
    After 7, capacity 2
    After 8, capacity 3
    5
    6
    7
    8
    9
    After 9, capacity 0
    After 10
    main() stopped
    

    This result might be the same as you saw in the playground. But, it is unpredictable because we can't determine how OS schedule the execution order of goroutines.

    If I run this code in my local machine, It will be different every time I execute it. See below:

    First run

    main() started
    After 1, capacity 1
    After 2, capacity 1
    After 3, capacity 2
    1
    2
    3
    4
    After 4, capacity 3
    After 5, capacity 0
    5
    After 6, capacity 0
    After 7, capacity 1
    After 8, capacity 2
    After 9, capacity 3
    6
    7
    8
    9
    10
    After 10
    main() stopped
    

    Second run

    main() started
    After 1, capacity 1
    After 2, capacity 2
    1
    2
    3
    After 3, capacity 2
    After 4, capacity 0
    4
    5
    After 5, capacity 1
    After 6, capacity 0
    After 7, capacity 1
    After 8, capacity 2
    6
    7
    8
    9
    After 9, capacity 3
    After 10
    main() stopped
    10
    

    So answers to your questions would be

    1. Receiving the channel (for 4) just was executed before fmt.Printf in the main function. It could be different every execution. Try it on your local machine.

    2. Similar to 1. It just read by range before len(c).

    3. The main goroutine exited before reading the 10 from the channel. In this case, you should wait for all items in the channel will be read using some techniques such as time sleeping, wait group of sync package or another channel.

    You can see how that program works by inserting the time.Sleep(time.Second) between the lines you want to inspect.

    For example, if you sleep for 1 second before fmt.Println("main() stopped"), you can see always the 10 echo because the main goroutine will be waiting for a second. (1 second is big enough for reading an item from the channel)

    package main
    
    import "fmt"
    import "time"
    
    func echo(c chan int) {
        for num := range c {
            fmt.Println(num)
        }
        fmt.Println("Done iterating")
    }
    
    func main() {
        fmt.Println("main() started")
        c := make(chan int, 3)
    
        go echo(c)
    
        c <- 1
        fmt.Printf("After 1, capacity %v
    ",len(c))
        c <- 2
        fmt.Printf("After 2, capacity %v
    ",len(c))
        c <- 3
        fmt.Printf("After 3, capacity %v
    ",len(c))
        c <- 4 // blocks here
        fmt.Printf("After 4, capacity %v
    ",len(c))
        c <- 5
        fmt.Printf("After 5, capacity %v
    ",len(c))
        c <- 6
        fmt.Printf("After 6, capacity %v
    ",len(c))
        c <- 7
        fmt.Printf("After 7, capacity %v
    ",len(c))
        c <- 8
        fmt.Printf("After 8, capacity %v
    ",len(c))
        c <- 9
        fmt.Printf("After 9, capacity %v
    ",len(c))
        c <- 10
        fmt.Printf("After 10
    ")
        time.Sleep(time.Second)
        fmt.Println("main() stopped")
    }
    
    评论

报告相同问题?

悬赏问题

  • ¥15 Python中的request,如何使用ssr节点,通过代理requests网页。本人在泰国,需要用大陆ip才能玩网页游戏,合法合规。
  • ¥100 为什么这个恒流源电路不能恒流?
  • ¥15 有偿求跨组件数据流路径图
  • ¥15 写一个方法checkPerson,入参实体类Person,出参布尔值
  • ¥15 我想咨询一下路面纹理三维点云数据处理的一些问题,上传的坐标文件里是怎么对无序点进行编号的,以及xy坐标在处理的时候是进行整体模型分片处理的吗
  • ¥15 CSAPPattacklab
  • ¥15 一直显示正在等待HID—ISP
  • ¥15 Python turtle 画图
  • ¥15 stm32开发clion时遇到的编译问题
  • ¥15 lna设计 源简并电感型共源放大器