Goroutines和Channels(四)

若是说goroutine是Go语言程序的并发体的话,那么channels则是它们之间的通讯机制。express

一个channel是一个通讯机制,它可让一个goroutine经过它给另外一个goroutine发送值信息。编程

每一个channel都有一个特殊的类型,也就是channels可发送数据的类型。一个能够发送int类型数据的channel通常写为chan int。缓存

 

使用内置的make函数,咱们能够建立一个channel:网络

ch := make(chan int) // ch has type 'chan int'

  

和map相似,channel也对应一个make建立的底层数据结构的引用。数据结构

当咱们复制一个channel或用于函数参数传递时,咱们只是拷贝了一个channel引用,所以调用者和被调用者将引用同一个channel对象。并发

和其它的引用类型同样,channel的零值也是nil。app

 

两个相同类型的channel可使用==运算符比较。若是两个channel引用的是相同的对象,那么比较的结果为真。一个channel也能够和nil进行比较。tcp

一个channel有发送和接受两个主要操做,都是通讯行为。函数

 

一个发送语句将一个值从一个goroutine经过channel发送到另外一个执行接收操做的goroutine。线程

发送和接收两个操做都使用<-运算符。

在发送语句中,<-运算符分割channel和要发送的值。

在接收语句中,<-运算符写在channel对象以前。一个不使用接收结果的接收操做也是合法的。

ch <- x  // a send statement
x = <-ch // a receive expression in an assignment statement
<-ch     // a receive statement; result is discarded

  

Channel还支持close操做,用于关闭channel,随后对基于该channel的任何发送操做都将致使panic异常。

对一个已经被close过的channel进行接收操做依然能够接受到以前已经成功发送的数据;若是channel中已经没有数据的话将产生一个零值的数据。

使用内置的close函数就能够关闭一个channel:

close(ch)

  

以最简单方式调用make函数建立的是一个无缓存的channel。

可是咱们也能够指定第二个整型参数,对应channel的容量。若是channel的容量大于零,那么该channel就是带缓存的channel。

 

ch = make(chan int)    // unbuffered channel
ch = make(chan int, 0) // unbuffered channel
ch = make(chan int, 3) // buffered channel with capacity 3

  

一个基于无缓存Channels的发送操做将致使发送者goroutine阻塞,直到另外一个goroutine在相同的Channels上执行接收操做,当发送的值经过Channels成功传输以后,两个goroutine能够继续执行后面的语句。反之,若是接收操做先发生,那么接收者goroutine也将阻塞,直到有另外一个goroutine在相同的Channels上执行发送操做。

 

基于无缓存Channels的发送和接收操做将致使两个goroutine作一次同步操做。由于这个缘由,无缓存Channels有时候也被称为同步Channels。当经过一个无缓存Channels发送数据时,接收者收到数据发生在唤醒发送者goroutine以前(译注:happens before,这是Go语言并发内存模型的一个关键术语!)。

 

在讨论并发编程时,当咱们说x事件在y事件以前发生(happens before),咱们并非说x事件在时间上比y时间更早;咱们要表达的意思是要保证在此以前的事件都已经完成了,例如在此以前的更新某些变量的操做已经完成,你能够放心依赖这些已完成的事件了。

 

当咱们说x事件既不是在y事件以前发生也不是在y事件以后发生,咱们就说x事件和y事件是并发的。这并非意味着x事件和y事件就必定是同时发生的,咱们只是不能肯定这两个事件发生的前后顺序。在下一章中咱们将看到,当两个goroutine并发访问了相同的变量时,咱们有必要保证某些事件的执行顺序,以免出现某些并发问题。(意思就是不肯定线程执行顺序)

 

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
}

  

当用户关闭了标准输入,主goroutine中的mustCopy函数调用将返回,而后调用conn.Close()关闭读和写方向的网络链接。

关闭网络链接中的写方向的链接将致使server程序收到一个文件(end-of-file)结束的信号。

关闭网络链接中读方向的链接将致使后台goroutine的io.Copy函数调用返回一个“read from closed connection”(“从关闭的链接读”)相似的错误,所以咱们临时移除了错误日志语句;(须要注意的是go语句调用了一个函数字面量,这Go语言中启动goroutine经常使用的形式。)

在后台goroutine返回以前,它先打印一个日志信息,而后向done对应的channel发送一个值。主goroutine在退出前先等待从done对应的channel接收一个值。所以,老是能够在程序退出前正确输出“done”消息。

基于channels发送消息有两个重要方面。首先每一个消息都有一个值,可是有时候通信的事实和发生的时刻也一样重要。当咱们更但愿强调通信发生的时刻时,咱们将它称为消息事件。有些消息事件并不携带额外的信息,它仅仅是用做两个goroutine之间的同步,这时候咱们能够用struct{}空结构体做为channels元素的类型,虽然也可使用bool或int类型实现一样的功能,done <- 1语句也比done <- struct{}{}更短。

相关文章
相关标签/搜索