2016-02-15 03:06:34 +00:00
### 8.4.1. 不带缓存的Channels
2016-01-02 14:05:49 +00:00
2016-02-15 03:06:34 +00:00
一个基于无缓存Channels的发送操作将导致发送者goroutine阻塞, 直到另一个goroutine在相同的Channels上执行接收操作, 当发送的值通过Channels成功传输之后, 两个goroutine可以继续执行后面的语句。反之, 如果接收操作先发生, 那么接收者goroutine也将阻塞, 直到有另一个goroutine在相同的Channels上执行发送操作。
2016-01-04 03:01:27 +00:00
2018-03-21 09:37:17 +00:00
基于无缓存Channels的发送和接收操作将导致两个goroutine做一次同步操作。因为这个原因, 无缓存Channels有时候也被称为同步Channels。当通过一个无缓存Channels发送数据时, 接收者收到数据发生在再次唤醒唤醒发送者goroutine之前( 译注: *happens before*, 这是Go语言并发内存模型的一个关键术语! ) 。
2016-01-04 03:01:27 +00:00
2016-02-15 03:06:34 +00:00
在讨论并发编程时, 当我们说x事件在y事件之前发生( *happens before*) , 我们并不是说x事件在时间上比y时间更早; 我们要表达的意思是要保证在此之前的事件都已经完成了, 例如在此之前的更新某些变量的操作已经完成, 你可以放心依赖这些已完成的事件了。
2016-01-04 03:01:27 +00:00
2016-02-15 03:06:34 +00:00
当我们说x事件既不是在y事件之前发生也不是在y事件之后发生, 我们就说x事件和y事件是并发的。这并不是意味着x事件和y事件就一定是同时发生的, 我们只是不能确定这两个事件发生的先后顺序。在下一章中我们将看到, 当两个goroutine并发访问了相同的变量时, 我们有必要保证某些事件的执行顺序, 以避免出现某些并发问题。
2016-01-04 03:01:27 +00:00
2016-02-15 03:06:34 +00:00
在8.3节的客户端程序, 它在主goroutine中( 译注: 就是执行main函数的goroutine) 将标准输入复制到server, 因此当客户端程序关闭标准输入时, 后台goroutine可能依然在工作。我们需要让主goroutine等待后台goroutine完成工作后再退出, 我们使用了一个channel来同步两个goroutine:
2016-01-04 03:01:27 +00:00
2016-01-21 02:39:06 +00:00
< u > < i > gopl.io/ch8/netcat3< / i > < / u >
2016-01-04 03:01:27 +00:00
```Go
func main() {
conn, err := net.Dial("tcp", "localhost:8000")
if err != nil {
log.Fatal(err)
}
done := make(chan struct{})
go func() {
io.Copy(os.Stdout, conn) // NOTE: ignoring errors
log.Println("done")
done < - struct { } { } / / signal the main goroutine
}()
mustCopy(conn, os.Stdin)
conn.Close()
< -done / / wait for background goroutine to finish
}
```
2017-12-02 08:08:06 +00:00
当用户关闭了标准输入, 主goroutine中的mustCopy函数调用将返回, 然后调用conn.Close()关闭读和写方向的网络连接。关闭网络连接中的写方向的连接将导致server程序收到一个文件( end-of-file) 结束的信号。关闭网络连接中读方向的连接将导致后台goroutine的io.Copy函数调用返回一个“read from closed connection”( “从关闭的连接读”) 类似的错误, 因此我们临时移除了错误日志语句; 在练习8.3将会提供一个更好的解决方案。( 需要注意的是go语句调用了一个函数字面量, 这是Go语言中启动goroutine常用的形式。)
2016-01-04 03:01:27 +00:00
2016-02-15 03:06:34 +00:00
在后台goroutine返回之前, 它先打印一个日志信息, 然后向done对应的channel发送一个值。主goroutine在退出前先等待从done对应的channel接收一个值。因此, 总是可以在程序退出前正确输出“done”消息。
2016-01-04 03:01:27 +00:00
2016-02-15 03:06:34 +00:00
基于channels发送消息有两个重要方面。首先每个消息都有一个值, 但是有时候通讯的事实和发生的时刻也同样重要。当我们更希望强调通讯发生的时刻时, 我们将它称为**消息事件**。有些消息事件并不携带额外的信息, 它仅仅是用作两个goroutine之间的同步, 这时候我们可以用`struct{}`空结构体作为channels元素的类型, 虽然也可以使用bool或int类型实现同样的功能, `done < - 1 ` 语句也比 ` done < - struct { } { } ` 更短 。
2016-01-04 03:01:27 +00:00
2016-10-05 05:42:01 +00:00
**练习 8.3: ** 在netcat3例子中, conn虽然是一个interface类型的值, 但是其底层真实类型是`*net.TCPConn`, 代表一个TCP连接。一个TCP连接有读和写两个部分, 可以使用CloseRead和CloseWrite方法分别关闭它们。修改netcat3的主goroutine代码, 只关闭网络连接中写的部分, 这样的话后台goroutine可以在标准输入被关闭后继续打印从reverb1服务器传回的数据。( 要在reverb2服务器也完成同样的功能是比较困难的; 参考**练习 8.4**。)
2016-01-04 03:01:27 +00:00