从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")
}

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")
}