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

一个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。

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

一个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{}{}更短。

Goroutines和Channels(四)的更多相关文章

  1. 如果这种方式导致程序明显变慢或者引起其他问题,我们要重新思考来通过 goroutines 和 channels 来解决问题

    https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/09.3.md 9.3 锁和 sync 包 在一些复杂的程序中,通常通 ...

  2. Goroutines和Channels

    原文链接 https://golangbot.com/goroutines/ Goroutines Goroutines 可以被认为是多个函数或方法同时允许.可以认为是一个轻量级的线程.与线程的花费相 ...

  3. Goroutines和Channels(五)

    Channels也可以用于将多个goroutine连接在一起,一个Channel的输出作为下一个Channel的输入.这种串联的Channels就是所谓的管道(pipeline).下面的程序用两个ch ...

  4. Goroutines和Channels(三)

    clock服务器每一个连接都会起一个goroutine.在本节中我们会创建一个echo服务器,这个服务在每个连接中会有多个goroutine.大多数echo服务仅仅会返回他们读取到的内容,就像下面这个 ...

  5. Goroutines和Channels(二)

    网络编程是并发大显身手的一个领域,由于服务器是最典型的需要同时处理很多连接的程序,这些连接一般来自于彼此独立的客户端. 本小节,我们会讲解go语言的net包,这个包提供编写一个网络客户端或者服务器程序 ...

  6. Goroutines和Channels(一)

    Go语言中的并发程序可以用两种手段来实现.本章讲解goroutine和channel,其支持“顺序通信进程”(communicating sequential processes)或被简称为CSP.C ...

  7. ARTS-S golang goroutines and channels(二)

    向tcp服务端发消息 package main import ( "io" "log" "net" "os" ) fun ...

  8. ARTS-S golang goroutines and channels(一)

    先用golang实现一个简单的tcp服务端,假定文件名为clock1.go // clock1.go package main import ( "fmt" "io&qu ...

  9. [PHP] 2018年终总结

    去掉敏感信息后的不完整版 ==========================================================================2018年12月29日 记 ...

随机推荐

  1. openstack 部署笔记--glance

    控制节点 创建用户及服务 $ . admin-openrc $ openstack user create --domain default --password-prompt glance $ op ...

  2. Logistic Regression Using Gradient Descent -- Binary Classification 代码实现

    1. 原理 Cost function Theta 2. Python # -*- coding:utf8 -*- import numpy as np import matplotlib.pyplo ...

  3. C语言标准函数源代码

    http://ftp.gnu.org/gnu/glibc/ 最新glibc-2.27.tar.gz 直接解压就可以

  4. mysql服务器上的mysql这个实例中表的介绍

    1.user表. 分个分隔符

  5. jq 自定义标注小组件 $.widget

    html 部分 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www ...

  6. isKindOfClass和isMemberOfClass 的区别

    判断对象类型 -(BOOL) isKindOfClass: classObj判断是否是这个类或者这个类的子类的实例 -(BOOL) isMemberOfClass: classObj 判断是否是这个类 ...

  7. eclipse启动 报错,错误信息为 return exit code=13

    打不开的报错如下图: 解决方法:手工配置Eclipse使用的JDK,在Eclipse的安装目录中找到eclipse.ini文件,增加正确的JDK安装目录,如图 在plugins/ 下一行,增加 -vm ...

  8. linux常用命令:gzip 命令

    减 少文件大小有两个明显的好处,一是可以减少存储空间,二是通过网络传输文件时,可以减少传输的时间.gzip是在Linux系统中经常使用的一个对文件进 行压缩和解压缩的命令,既方便又好用.gzip不仅可 ...

  9. linux常用命令:grep 命令

    Linux系统中grep命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹 配的行打印出来.grep全称是Global Regular Expression Print,表示全局正则表达 ...

  10. 简单理解offsetleft、offsetTop、offsetParent

    先来看看offsetParent返回的是什么值 ele.offsetParent返回的是ele元素最近的并且是定位过(relative,absolute)的父元素,如果没有父元素或者是父元素中没有一个 ...