英文:
How does make(chan bool) behave differently from make(chan bool, 1)?
问题
我的问题源于尝试使用select
语句来读取或写入一个通道。
我知道像make(chan bool, 1)
这样指定的通道是有缓冲的,我的问题的一部分是这种方式与make(chan bool)
之间的区别是什么,这个页面说它与make(chan bool, 0)
是一样的---一个能容纳0个值的通道有什么意义呢?
请参考**playground A**:
chanFoo := make(chan bool)
for i := 0; i < 5; i++ {
select {
case <-chanFoo:
fmt.Println("Read")
case chanFoo <- true:
fmt.Println("Write")
default:
fmt.Println("Neither")
}
}
A输出:
Neither
Neither
Neither
Neither
Neither
(移除default
语句会导致死锁!!)
现在请参考**playground B**:
chanFoo := make(chan bool, 1) // 唯一的区别是缓冲区大小为1
for i := 0; i < 5; i++ {
select {
case <-chanFoo:
fmt.Println("Read")
case chanFoo <- true:
fmt.Println("Write")
default:
fmt.Println("Neither")
}
}
B输出:
Write
Read
Write
Read
Write
在我的情况下,B输出是我想要的结果。无缓冲的通道有什么好处呢?我在golang.org上看到的所有示例似乎都是使用它们来一次发送一个信号/值(这正是我所需要的)---但是就像在playground A中一样,通道从未被读取或写入。我在对通道的理解上漏掉了什么吗?
英文:
My question arises from trying to read a channel, if I can, or write it, if I can, using a select
statement.
I know that channels specified like make(chan bool, 1)
are buffered, and part of my question is what is the difference between that, and make(chan bool)
-- which this page says is the same thing as make(chan bool, 0)
--- what is the point of a channel that can fit 0 values in it?
See playground A:
chanFoo := make(chan bool)
for i := 0; i < 5; i++ {
select {
case <-chanFoo:
fmt.Println("Read")
case chanFoo <- true:
fmt.Println("Write")
default:
fmt.Println("Neither")
}
}
A output:
Neither
Neither
Neither
Neither
Neither
(Removing the default
case results in a deadlock!!)
Now see playground B:
chanFoo := make(chan bool, 1) // the only difference is the buffer size of 1
for i := 0; i < 5; i++ {
select {
case <-chanFoo:
fmt.Println("Read")
case chanFoo <- true:
fmt.Println("Write")
default:
fmt.Println("Neither")
}
}
B output:
Write
Read
Write
Read
Write
In my case, B output is what I want. What good are unbuffered channels? All the examples I see on golang.org appear to use them to send one signal/value at a time (which is all I need) -- but as in playground A, the channel never gets read or written. What am I missing here in my understanding of channels?
答案1
得分: 15
一个可以容纳0个值的通道的意义是什么?
首先,我想指出这里的第二个参数表示缓冲区大小,所以这只是一个没有缓冲区的通道(无缓冲通道)。
实际上,这就是为什么会出现你的问题的原因。只有在有人阻塞读取通道时,无缓冲通道才能被写入,这意味着你需要使用一些协程来处理,而不是只有一个协程。
另外,请参考《Go内存模型》:
从无缓冲通道接收操作发生在该通道上的发送操作完成之前。
英文:
> what is the point of a channel that can fit 0 values in it
First I want to point out that the second parameter here means buffer size, so that is simply a channel without buffers (un-buffered channel).
Actually that's the reason why your problem is generated. Un-buffered channels are only writable when there's someone blocking to read from it, which means you shall have some coroutines to work with -- instead of this single one.
Also see The Go Memory Model:
> A receive from an unbuffered channel happens before the send on that channel completes.
答案2
得分: 4
一个非缓冲通道意味着对通道的读取和写入是阻塞的。
在select
语句中:
- 如果其他goroutine当前被阻塞在向通道写入数据,读取操作将成功。
- 如果其他goroutine当前被阻塞在从通道读取数据,写入操作将成功。
- 否则,将执行
default
语句,这在你的情况A中发生。
英文:
An unbuffered channel means that read and writes from and to the channel are blocking.
In a select
statement:
- the read would work if some other goroutine was currently blocked in writing to the channel
- the write would work if some other goroutine was currently blocked in reading to the channel
- otherwise the
default
case is executed, which happens in your case A.
答案3
得分: 4
无缓冲通道(即没有容量的通道)会阻塞发送者,直到有人从中读取数据。为了使其按照你的期望工作,你应该使用两个 goroutine 来避免在同一线程中发生死锁。
例如,使用以下代码:http://play.golang.org/p/KWJ1gbdSqf
它还包括一个机制,用于在两个 goroutine 都完成时让主函数检测到。
package main
import "fmt"
func send(out, finish chan bool) {
for i := 0; i < 5; i++ {
out <- true
fmt.Println("Write")
}
finish <- true
close(out)
}
func recv(in, finish chan bool) {
for _ = range in {
fmt.Println("Read")
}
finish <- true
}
func main() {
chanFoo := make(chan bool)
chanfinish := make(chan bool)
go send(chanFoo, chanfinish)
go recv(chanFoo, chanfinish)
<-chanfinish
<-chanfinish
}
它不会显示交替的读取和写入,因为一旦 send
写入通道,它就会被阻塞,无法打印 "Write",所以执行流程转移到 recv
,它接收通道并打印 "Read"。它将尝试再次读取通道,但会被阻塞,执行流程转移到 send
。现在,send
可以写入第一个 "Write",发送到通道(不会阻塞,因为现在有一个接收者准备好了),并写入第二个 "Write"。无论如何,这并不是确定性的,调度器可以在最新的 1.2 版本中在任何时候将执行流程移动到任何其他正在运行的 goroutine。
英文:
Unbuffered channels (created without capacity) will block the sender until somebody can read from them, so to make it work as you expect, you should use two goroutines to avoid the deadlock in the same thread.
For example, with this code: http://play.golang.org/p/KWJ1gbdSqf
It also includes a mechanism for the main func to detect when both goroutines have finished.
package main
import "fmt"
func send(out, finish chan bool) {
for i := 0; i < 5; i++ {
out <- true
fmt.Println("Write")
}
finish <- true
close(out)
}
func recv(in, finish chan bool) {
for _ = range in {
fmt.Println("Read")
}
finish <- true
}
func main() {
chanFoo := make(chan bool)
chanfinish := make(chan bool)
go send(chanFoo, chanfinish)
go recv(chanFoo, chanfinish)
<-chanfinish
<-chanfinish
}
It won't show the alternate Read and Write, because as soon as send
writes to the channel, it is blocked, before it can print the "Write", so the execution moves to recv
that receives the channel and prints "Read". It will try to read the channel again but it will be blocked and the execution moves to send
. Now send
can write the first "Write", send to the channel (without blocking because now there is a receiver ready) and write the second "Write". In any case, this is not deterministic and the scheduler may move the execution at any point to any other running goroutine (at least in the latest 1.2 release).
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论