golang缓冲通道意外结果

package main
import (
    "time"
    "runtime"
)

var c = make(chan int, 2)

func main() {
  go worker(1)
  for i := 0; i < 30; i++ {
    go func() {
            // k := i   make a local copy not make any difference
            c <- i
    }()
  }
  time.Sleep(100* time.Second)
}

func worker(id int) {
  for {
    a := <-c
    println(a, runtime.NumGoroutine())
    time.Sleep(time.Second)
  }
}

the output is unpredictable, sometimes like below.

7 9
13 29
13 28
13 27
13 26
13 25
13 24
13 23
16 22
16 21
17 20
19 19
21 18
21 17
23 16
25 15
26 14
26 13
26 12
26 11
26 10
26 9
26 8
26 7
27 6
27 5
13 4
28 3
30 2
30 2

I know sender will block if the buffer channel is full, and when channel is available the sender can continue.

  1. why output is not constant output 0-29? how to make it??
  2. how variable/local variable store in goroutine??
  3. if lots of senders are blocked, are they waked up by FIFO order??

Output is not constant because different goroutines share same local variable i. If you uncomment your line and move it right before the goruoutine call, you'll see constant output 0-29. The better way is to move i variable to goroutine function arguments.

Wake up order is not specified in specs. You should consider it as a random one.

3 It is FIFO

1 Because the goroutines created inside the for loop will not necessarily execute sequentially. The underlying Go scheduler will start one randomly (it's how channels dispatch their values). Of-course all of them will get created, but they will (get scheduled to) start at the point time.Sleep(...) in main is called (Go scheduler is a cooperative one and does so on certain points like function calls, channel ops, etc - for example this)

2 Use the channel directly:

var (
    c  = make(chan int, 2)
    wg = &sync.WaitGroup{}
)

func main() {
    wg.Add(1)
    go worker(1)

    wg.Add(1)
    go func() {
        defer wg.Done()
        for i := 0; i < 30; i++ {
            c <- i
        }
        close(c)
    }()
    wg.Wait()
}

func worker(id int) {
    defer wg.Done()
    for a := range c {
        println(a, runtime.NumGoroutine())
        time.Sleep(time.Second)
    }
}

About passing a for loop variable; you've done it almost correctly. You just have to put the line to create the local closure, outside the goroutine:

var (
    wg = &sync.WaitGroup{}
)

func main() {
    for i := 0; i < 3; i++ {
        localClosure := i // <- this line

        wg.Add(1)
        go func() {
            defer wg.Done()
            println(localClosure)
        }()
    }

    wg.Wait()
}