Go Channel 写入超出其容量的数据

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

Go Channel writes data beyond it's capacity

问题

package main

import (
	"fmt"
	"time"
)

func write(ch chan int) {
	for i := 0; i < 5; i++ {
		fmt.Println("可用", i)
		ch <- i
		fmt.Println("成功写入", i, "到 ch")
	}

	close(ch)
}

func main() {
	ch := make(chan int)

	go write(ch)

	time.Sleep(time.Second)

	for v := range ch {
		fmt.Println("从 ch 读取值", v)

		time.Sleep(time.Second)
	}
}

输出

可用 0
从 ch 读取值 0
成功写入 0 到 ch
可用 1
从 ch 读取值 1
成功写入 1 到 ch
可用 2
从 ch 读取值 2
成功写入 2 到 ch
可用 3
从 ch 读取值 3
成功写入 3 到 ch
可用 4
从 ch 读取值 4
成功写入 4 到 ch

由于这是一个无缓冲通道,它应该在数据写入后立即阻塞,直到另一个 goroutine 从同一通道读取。但它接受超出其容量的数据。

预期行为

package main

import (
	"fmt"
	"time"
)

func write(ch chan int) {
	for i := 0; i < 5; i++ {
		fmt.Println("可用", i)
		ch <- i
		fmt.Println("成功写入", i, "到 ch")
	}

	close(ch)
}

func main() {
	ch := make(chan int)

	go write(ch)

	time.Sleep(time.Second)

	for v := range ch {
		fmt.Println("从 ch 读取值", v)

		time.Sleep(time.Second)
	}
}

输出

可用 0
从 ch 读取值 0
成功写入 0 到 ch
可用 1
从 ch 读取值 1
成功写入 1 到 ch
可用 2
从 ch 读取值 2
成功写入 2 到 ch
可用 3
从 ch 读取值 3
成功写入 3 到 ch
可用 4
从 ch 读取值 4
成功写入 4 到 ch

如果在代码中放置一些计时器,以便在每次迭代之前阻塞主 goroutine,它将按预期工作。

英文:
package main

import (
	&quot;fmt&quot;
)

func write(ch chan int) {
	for i := 0; i &lt; 5; i++ {
		fmt.Println(&quot;avaliable&quot;, i)
		ch &lt;- i
		fmt.Println(&quot;successfully wrote&quot;, i, &quot;to ch&quot;)
	}

	close(ch)
}

func main() {
	ch := make(chan int)

	go write(ch)

	for v := range ch {
		fmt.Println(&quot;read value&quot;, v, &quot;from ch&quot;)
	}
}

Output

avaliable 0
successfully wrote 0 to ch
avaliable 1
read value 0 from ch
read value 1 from ch
successfully wrote 1 to ch
avaliable 2
successfully wrote 2 to ch
avaliable 3
read value 2 from ch
read value 3 from ch
successfully wrote 3 to ch
avaliable 4
successfully wrote 4 to ch
read value 4 from ch

Since this is a unbuffered channel it should have blocked as soon as data is written into it until another goroutine reads from the same channel. But it accepts data beyond it's capacity.

Intended Behavior

package main

import (
	&quot;fmt&quot;
	&quot;time&quot;
)

func write(ch chan int) {
	for i := 0; i &lt; 5; i++ {
		fmt.Println(&quot;avaliable&quot;, i)
		ch &lt;- i
		fmt.Println(&quot;successfully wrote&quot;, i, &quot;to ch&quot;)
	}

	close(ch)
}

func main() {
	ch := make(chan int)

	go write(ch)

	time.Sleep(time.Second)

	for v := range ch {
		fmt.Println(&quot;read value&quot;, v, &quot;from ch&quot;)

		time.Sleep(time.Second)
	}
}

Output

avaliable 0
read value 0 from ch
successfully wrote 0 to ch
avaliable 1
read value 1 from ch
successfully wrote 1 to ch
avaliable 2
read value 2 from ch
successfully wrote 2 to ch
avaliable 3
read value 3 from ch
successfully wrote 3 to ch
avaliable 4
read value 4 from ch
successfully wrote 4 to ch

If some timers are placed throughout the code so that the main goroutine is blocked before each iteration it works as expected.

答案1

得分: 2

你的输出中不能推断任何东西,因为在“读取值”和“成功写入”Printfs之间没有顺序保证。(在“可用”Printf之间有一个顺序保证,它发生在通道发送之前,以及“读取值”Printf之间,它发生在相应的通道接收之后,你可以看到在你的输出中这个顺序从未被违反)。

该通道没有缓冲任何内容,因为它没有缓冲区;只是在通道发送完成后,两个不同的goroutine以不确定的顺序运行。有时发送方先执行并打印“成功写入”消息,有时接收方先执行并打印“读取值”消息。它们之间从未“超前”超过一个值,因为它们仍然完全同步于通道发送;它们只是在竞争立即打印状态消息。

当你在主函数中添加Sleep调用时,恰好使得运行write的goroutine始终被阻塞在等待发送下一个值上,而运行main的goroutine则在调用Sleep时被阻塞。当计时器到期时,主goroutine被唤醒,立即发现通道上有等待它的东西,获取它并再次进入睡眠状态,然后write goroutine被唤醒。通过减慢速度,你已经使调度程序以一致的顺序运行事务(尽管部分是运气的问题);如果没有睡眠,一切都以最快的速度运行,结果显然是随机的。

英文:

You can't infer anything from your output, because there is no ordering guarantee between the "read value" and "successfully wrote" Printfs executing. (There is one between the "available" Printf, which occurs before the channel send, and the "read value" Printf, which occurs after the corresponding channel receive, and you can see that that ordering is never violated in your output).

The channel isn't buffering anything, because it has no buffer; it's just that the two different goroutines run in an indeterminate order after the channel send completes. Sometimes the sending side gets to go first and prints the "successfully wrote" message, and sometimes the receiving side gets to go first and prints the "read value" message. Neither one ever "gets ahead" by more than one value, because they're still fully synchronized on the channel send; they're just racing to print their status messages immediately after.

When you add the Sleep calls to main, it just so happens to make it so that the goroutine running write will always be blocked on waiting to send the next value, while the one running main blocks on the call to Sleep. When the timer expires, the main goroutine wakes up, and immediately finds that it has something waiting for it on the channel, grabs it, and goes back to sleep, and then the write goroutine gets woken up. By slowing things down you've gotten the scheduler to run things in a consistent order (although it's still partly a matter of luck); without the sleeps, with everything running as fast as it can, the result is apparently random.

答案2

得分: 1

如果是这样的话,“休眠”版本的输出将会更加混乱。

即使两个独立的线程在某个点上同步,你也不能期望得到一个连贯的输出。

英文:

If that were the case, the output of the "sleeping" version would be even more chaotic.

You cannot expect a coherent output from two independent threads even if they are synchronized at some point.

huangapple
  • 本文由 发表于 2022年1月2日 12:54:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/70553686.html
匿名

发表评论

匿名网友

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

确定