我正在学习Go,到目前为止对它印象非常深刻.我已经阅读了golang.org上的所有在线文档,并且在Chrisnall的"The Go Programming Language Phrasebook"中途.我得到了渠道的概念,并认为它们非常有用.但是,我一定错过了一些重要的事情,因为我看不到单向频道的重点.
如果我正确地解释它们,只能接收只读通道并且只能发送只写通道,那么为什么要有一个可以发送到的通道而且从不接收通道?他们可以从一个"方向"转向另一个吗?如果是这样,那么,如果没有实际约束,那又有什么意义呢?它们只不过是对渠道目的的客户代码的暗示吗?
jim*_*imt 86
对于接收它的任何人,只读通道可以是只读的,而发送器仍然具有可以写入的双向通道.例如:
func F() <-chan int {
// Create a regular, two-way channel.
c := make(chan int)
go func() {
defer close(c)
// Do stuff
c <- 123
}()
// Returning it, implicitely converts it to read-only,
// as per the function return value.
return c
}
Run Code Online (Sandbox Code Playgroud)
谁打电话F(),接收他们只能阅读的频道.这对于在编译时捕获通道的潜在错误使用非常有用.因为只读/只写通道是不同类型,所以编译器可以使用其现有的类型检查机制来确保调用者不会尝试将内容写入它没有业务写入的通道.
我认为只读渠道的主要动机是防止渠道的腐败和恐慌.想象一下,如果你能写入返回的频道time.After.这可能会搞砸很多代码.
此外,如果您:
这些操作是只读通道的编译时错误,但是当多个go-routines可以写入/关闭通道时,它们会导致恶劣的竞争条件.
解决这个问题的一种方法是永远不要关闭频道并让它们被垃圾收集.但是,close它不仅仅用于清理,而且实际上在通道超出范围时使用:
func consumeAll(c <-chan bool) {
for b := range c {
...
}
}
Run Code Online (Sandbox Code Playgroud)
如果通道永远不会关闭,则此循环将永远不会结束.如果多个常规程序正在写入一个频道,那么就会有大量的簿记需要继续决定哪一个会关闭频道.
由于无法关闭只读通道,因此可以更轻松地编写正确的代码.正如@jimt在他的评论中指出的那样,你不能将只读通道转换为可写通道,所以你可以保证只有部分可以访问可写通道版本的代码可以关闭/写入它.
编辑:
至于有多个读者,这是完全没问题,只要你考虑到它.当在生产者/消费者模型中使用时,这尤其有用.例如,假设您有一个TCP服务器,它只接受连接并将它们写入工作线程的队列:
func produce(l *net.TCPListener, c chan<- net.Conn) {
for {
conn, _ := l.Accept()
c<-conn
}
}
func consume(c <-chan net.Conn) {
for conn := range c {
// do something with conn
}
}
func main() {
c := make(chan net.Conn, 10)
for i := 0; i < 10; i++ {
go consume(c)
}
addr := net.TCPAddr{net.ParseIP("127.0.0.1"), 3000}
l, _ := net.ListenTCP("tcp", &addr)
produce(l, c)
}
Run Code Online (Sandbox Code Playgroud)
可能您的连接处理比接受新连接需要更长的时间,因此您希望拥有一个生产者的许多消费者.多个生成器更难(因为您需要协调关闭通道的人),但您可以在通道发送中添加某种信号量样式的通道.
Go 通道以 Hoare 的 Communicating Sequential Processes 为模型,这是一种并发的进程代数,面向通信参与者(小“a”)之间的事件流。因此,通道有一个方向,因为它们有发送端和接收端,即事件的生产者和事件的消费者。类似的模型也用于 Occam 和 Limbo。
这很重要 - 如果可以在不同时间任意将通道端重新用作发送方和接收方,则很难推断死锁问题。