Golang缓冲通道意外结果

huangapple go评论87阅读模式
英文:

golang buffer channel unexpected result

问题

以下是翻译好的代码:

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

输出是不可预测的,有时候像下面这样:

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

我知道如果缓冲通道已满,发送者将被阻塞,当通道可用时,发送者可以继续发送。

  1. 为什么输出不是常量输出0-29?如何使其成为常量输出?
  2. 变量/局部变量在goroutine中如何存储?
  3. 如果有很多发送者被阻塞,它们按照FIFO顺序被唤醒吗?
英文:
package main
import (
    &quot;time&quot;
    &quot;runtime&quot;
)

var c = make(chan int, 2)

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

func worker(id int) {
  for {
    a := &lt;-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??

答案1

得分: 2

输出不是常量,因为不同的goroutine共享相同的局部变量i。如果你取消注释你的那一行,并将其移到goroutine调用之前,你会看到常量输出0-29。更好的方法是将i变量移到goroutine函数的参数中。

唤醒顺序在规范中没有指定。你应该将其视为随机的。

英文:

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.

答案2

得分: 0

3 这是先进先出(FIFO)。

1 因为在for循环内创建的goroutine不一定按顺序执行。Go调度器会随机启动一个(这是通道分发值的方式)。当然,它们都会被创建,但它们会在main函数中的time.Sleep(...)调用的时候(Go调度器是协作式的,在函数调用、通道操作等特定点上进行调度,例如这里)开始(被调度)。

2 直接使用通道:

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

关于传递for循环变量,你几乎做得正确。你只需要将创建局部闭包的行放在goroutine之外:

var (
    wg = &sync.WaitGroup{}
)

func main() {
    for i := 0; i < 3; i++ {
        localClosure := i // <- 这一行

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

    wg.Wait()
}
英文:

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 = &amp;sync.WaitGroup{}
)

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

	wg.Add(1)
	go func() {
		defer wg.Done()
		for i := 0; i &lt; 30; i++ {
			c &lt;- 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 = &amp;sync.WaitGroup{}
)

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

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

	wg.Wait()
}

huangapple
  • 本文由 发表于 2017年3月28日 12:21:23
  • 转载请务必保留本文链接:https://go.coder-hub.com/43060591.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定