Go的单向通道有什么意义?

我正在学习Go,至今对它印象非常深刻。 我已经阅读了golang.org上的所有在线文档,并且已经在Chrisnall的“The Go Programming Language Phrasebook”的一半了。 我得到渠道的概念,并认为他们将是非常有用的。 但是,我一定错过了一些重要的事情,因为我看不到单向的渠道。

如果我正确地解释它们,一个只读频道只能被接收,一个只写频道只能被传输,那么为什么有一个频道可以发送到永远不会收到? 他们可以从一个“方向”投向另一个吗? 如果是这样,再次,如果没有实际的限制,有什么意义呢? 它们只不过是暗示渠道目的的客户代码?

一个只读通道可以只读给接收它的人,而发送者仍然有一个双向通道,他们可以写。 例如:

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 } 

谁曾经呼叫F() ,接收他们只能阅读的频道。 这在编译时捕获一个通道的潜在错误使用是非常有用的。 由于只读通道是不同的types,因此编译器可以使用其现有的types检查机制来确保调用者不会尝试将内容写入没有业务写入的通道。

我认为只读渠道的主要动机是防止渠道的腐败和恐慌。 想象一下,如果你能写入由时间返回的频道。 这可能会弄乱很多代码。

另外,如果您:

  • closures一个频道不止一次
  • 写封闭的频道

这些操作是只读通道的编译时错误,但当多个go-routines可以写入/closures通道时,它们可能会导致恶劣的竞争状态。

解决这个问题的一个方法就是不要closures频道,让它们被垃圾收集。 但是, close不仅仅是用于清理,而且当通道被排除时,它实际上已经使用了:

 func consumeAll(c <-chan bool) { for b := range c { ... } } 

如果通道从未closures,则此循环将永不结束。 如果有多个例程正在写入某个频道,那么还有很多簿记必须继续,以决定哪一个将closures该频道。

由于您无法closures只读通道,因此可以更轻松地编写正确的代码。 正如@jimt在他的评论中指出的那样,你不能将只读通道转换为可写通道,所以你可以保证只有访问可写通道版本的部分代码才能closures/写入通道。

编辑:

至于有多个读者,这个完全没有问题,只要你说清楚。 在生产者/消费者模型中使用时,这是特别有用的。 例如,假设您有一个只接受连接并将它们写入工作线程队列的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) } 

可能你的连接处理比接受一个新的连接需要更长的时间,所以你希望拥有一个生产者的消费者。 多个制作者更难(因为你需要协调谁closures频道),但是你可以添加某种信号灯风格的频道到频道发送。

Go频道模仿Hoare的Communicating Sequential Processes,一个面向并发的stream程代数,面向通信angular色之间的事件stream(小“a”)。 因此,渠道有一个方向,因为他们有一个发送端和一个接收端,即事件的生产者和事件的消费者。 Occam和Limbo也使用了类似的模型。

这很重要 – 如果一个通道端可以在不同的时间被任意地重新用作发送端和接收端,那么很难推断出死锁问题。