不带缓存的Channels
一个基于无缓存Channels的发送操作将导致发送者goroutine阻塞,直到另一个goroutine在相同的Channels上执行接收操作,当发送的值通过Channels成功传输之后,两个goroutine可以继续执行后面的语句。反之,如果接收操作先发生,那么接收者goroutine也将阻塞,直到有另一个goroutine在相同的Channels上执行发送操作。
基于无缓存Channels的发送和接收操作将导致两个goroutine做一次同步操作。因为这个原因,无缓存Channels有时候也被称为同步Channels。当通过一个无缓存Channels发送数据时,接收者收到数据发生在唤醒发送者goroutine之前(
在讨论并发编程时,当我们说x事件在y事件之前发生(happens before),我们并不是说x事件在时间上比y时间更早;我们要表达的意思是要保证在此之前的事件都已经完成了,例如在此之前的更新某些变量的操作已经完成,你可以放心依赖这些已完成的事件了。
当我们说x事件既不是在y事件之前发生也不是在y事件之后发生,我们就说x事件和y事件是并发的。这并不是意味着x事件和y事件就一定是同时发生的,我们只是不能确定这两个事件发生的先后顺序。在下一章中我们将看到,当两个goroutine并发访问了相同的变量时,我们有必要保证某些事件的执行顺序,以避免出现某些并发问题。
package main
/*
基于无缓冲的channel的main和 goroutine的同步
*/
import (
"io"
"log"
"net"
"os"
)
func main() {
conn, err := net.Dial("tcp", "127.0.0.1:8001")
if err != nil {
log.Fatal(err)
}
done := make(chan string)
go func() {
io.Copy(os.Stdout, conn)
log.Println("groutine: done!")
done <- "I am done"
}()
//从客户端输入,将客户端标输入的数据发给客户端套接字
io.Copy(conn, os.Stdin)
conn.Close() //此时main要主动关闭conn, 否则goroutine里面的io.Copy()会一直阻塞等待conn
log.Println("main wait goroutine...")
<-done
log.Println("main: done!")
//这样我们就保证了 "main::done!"打印之前 一定先打印"groutine:done!"
}
基于channels发送消息有两个重要方面。首先每个消息都有一个值,但是有时候通讯的事实和发生的时刻也同样重要。当我们更希望强调通讯发生的时刻时,我们将它称为消息事件。有些消息事件并不携带额外的信息,它仅仅是用作两个goroutine之间的同步,这时候我们可以用struct{}
空结构体作为channels元素的类型,虽然也可以使用bool或int类型实现同样的功能,done <- 1
语句也比done <- struct{}{}
更短。