我的问题源于尝试阅读频道(如果可以的话(或编写频道(如果可以的话(使用select
语句。
我知道像make(chan bool, 1)
这样指定的通道是缓冲的,我的部分问题是它和make(chan bool)
有什么区别——这个页面说这与make(chan bool, 0)
---可以容纳 0 个值的通道有什么意义?
查看游乐场 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")
}
}
输出:
Neither
Neither
Neither
Neither
Neither
(删除default
案例会导致死锁!!
现在看操场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 输出:
Write
Read
Write
Read
Write
就我而言,B 输出是我想要的。无缓冲通道有什么用?我在 golang.org 上看到的所有示例似乎都使用它们一次发送一个信号/值(这就是我所需要的(——但就像在操场 A 中一样,通道永远不会被读取或写入。在我对渠道的理解中,我错过了什么?
可以容纳 0 个值的通道有什么意义
首先,我想指出这里的第二个参数表示缓冲区大小,因此这只是一个没有缓冲区的通道(无缓冲通道(。
实际上,这就是产生问题的原因。未缓冲的通道只有在有人阻止从中读取时才是可写的,这意味着您应该使用一些协程 - 而不是这个协程。
另请参阅 Go 内存模型:
来自未缓冲通道的接收发生在该通道上的发送完成之前。
无缓冲通道意味着从通道读取和写入通道是阻塞的。
在select
声明中:
- 如果当前阻止了其他一些 goroutine,则读取将起作用,写入通道
- 如果当前在读取通道时阻止了其他 goroutine,则写入将起作用
- 否则,将执行
default
案例,这发生在您的案例 A 中。
无缓冲的通道(在没有容量的情况下创建(将阻止发送方,直到有人可以从中读取,因此要使其按预期工作,您应该使用两个 goroutines 来避免同一线程中的死锁。
例如,使用此代码:http://play.golang.org/p/KWJ1gbdSqf
它还包括一个机制,用于主函数检测两个 goroutines 何时完成。
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
写入通道,它就会被阻止,然后它才能打印"写入",因此执行移动到接收通道并打印"读取"的recv
。它将尝试再次读取通道,但它将被阻止,执行将移至send
。现在send
可以写入第一个"写入",发送到通道(没有阻塞,因为现在有一个接收器准备好了(并写入第二个"写入"。在任何情况下,这不是确定性的,调度程序可以随时将执行移动到任何其他正在运行的goroutine(至少在最新的1.2版本中(。