Go语言提供的消息通信机制被称为channel。

不要通过共享内存来通信,而应该通过通信来共享内存。

channel是Go语言在语言级别提供的goroutine间的通信方式。我们可以使用channel在两个或多个goroutine之间传递消息。channel是进程间的通信方式,因此通过channel传递对象的过程和调用函数时的参数传递行为比较一致,比如也可以传递指针。如果需要跨进程通信,建议使用分布式系统的方法来解决,比如使用Socket或HTTP等通信协议。

channel是类型相关的。也就是说,一个channel只能传递一种类型的值,这个类型需要在声明channel时指定。

基本语法 一般channel的声明形式为:

  1. var chanName chan ElementType

与一般的变量声明不同的地方仅仅是在类型之前加了chan关键字。ElementType指定这个channel所能传递的元素类型。
定义一个channel也很简单,直接使用内置的函数make()即可:

  1. ch := make(chan int)

这就声明了并初始化了一个int型的名为chan的channel。
在channel的用法中,最常见的包括写入和读出。将一个数据写入(发送)至channel的语法很直观,如下:

  1. ch <- value

向channel写入数据通常会导致程序阻塞,直到有其他goroutine从这个channel中读取数据。从channel中读取数据的语法是

  1. value := <- ch

如果channel之前没有写入数据,那么从channel中读取数据也会导致程序阻塞,直到channel中被写入数据为止。我们之后还会提到如何控制channel只接受写或者只允许读取,即单向channel。

select

  1. Go语言在语言级别支持select关键字,用于处理异步IO问题。<br /> select的用法和switch语法非常类似,由select开始一个新的选择块,每个选择条件由case语句描述。与switch语句可以选择任何可使用相等比较的条件相比,select有比较多的限制,其中最大的一条限制就是每个case语句里必须是一个IO操作,大致的结构如下:


Go并发编程之channel - 图1
可以看出,select不像switch,后面并不带判断条件,而是直接去查看case语句。每个case语句都必须是一个面向channel的操作。

缓冲机制

要创建一个带缓冲的channel,其实也非常容易:
Go并发编程之channel - 图2

在调用make()时将缓冲区大小作为第二个参数传入即可,上面这个例子就创建了一个大小为1024的int类型channel,即使没有读取方,写入方也可以一直往channel里写入,在缓冲区被填完之前都不会阻塞。

从带缓冲的channel中读取数据可以使用与常规非缓冲channel完全一致的方法,但我们也可以使用range关键来实现更为简便的循环读取:
Go并发编程之channel - 图3

超时机制

Go语言没有提供直接的超时处理机制,但我们可以利用select机制。虽然select机制不是专为超时而设计的,却能很方便地解决超时问题。因为select的特点是只要其中一个case已经完成,程序就会继续往下执行,而不会考虑其他case的情况。

基于此特性,我们来为channel实现超时机制:
Go并发编程之channel - 图4
这样使用select机制可以避免永久等待的问题,因为程序会在timeout中获取到一个数据后继续执行,无论对ch的读取是否还处于等待状态,从而达成1秒超时的效果。

这种写法看起来是一个小技巧,但却是在Go语言开发中避免channel通信超时的最有效方法。在实际的开发过程中,这种写法也需要被合理利用起来,从而有效地提高代码质量。

channel的传递

在Go语言中channel本身也是一个原生类型,与map之类的类型地位一样,因此channel本身在定义后也可以通过channel来传递。

利用channel可被传递的特性来实现我们的管道。为了简化表达,我们假设在管道中传递的数据只是一个整型数,在实际的应用场景中这通常会是一个数据块。
Go并发编程之channel - 图5

单向channel

单向channel只能用于发送或者接收数据。 channel本身必然是同时支持读写的,否则根本没法用。假如一个channel真的只能读,那么肯定只会是空的,因为你没机会往里面写数据。同理,如果一个channel只允许写,即使写进去了,也没有丝毫意义,因为没有机会读取里面的数据。所谓的单向channel概念,其实只是对channel的一种使用限制。

我们在将一个channel变量传递到一个函数时,可以通过将其指定为单向channel变量,从而限制 该函数中可 以对此 channel的操作, 比如只能往 这个 channel写,或者只 能从这个channel读。

单向channel变量的声明如下:
Go并发编程之channel - 图6

  1. channel是一个原生类型,因此不仅支持被传递,还支持类型转换。只有在介绍了单向channel的概念后,读者才会明白类型转换对于channel的意义:就是在单向channel和双向channel之间进行转换。示例如下:<br />![](https://cdn.nlark.com/yuque/0/2018/png/200878/1544784427426-4b080d02-f011-438b-bfd7-330856177401.png#width=827)

关闭channel

关闭channel非常简单,直接使用Go语言内置的close()函数即可:

  1. close(ch)

如何判断一个channel是否已经被关闭?我们可以在读取的时候使用多重返回值的方式:

  1. x, ok := <-ch

这个用法与map中的按键获取value的过程比较类似,只需要看第二个bool返回值即可,如果返回值是false则表示ch已经被关闭。


Go并发编程之channel - 图7