1. Channel是什么,怎样使用它?

Channel类似Unix管道,多个goroutine利用channel通信来传递数据,channel和goroutine一同构成了Go语言特有的CSP(communicating sequential processes,通信顺序进程)并发模型,goroutine是CSP模型中的实体,channel是通道。

makenilmake
chan T          // 可以对通道发送和接收类型T的值
chan<- float64  // 可以对通道发送类型float64的值
<-chan int      // 可以对通道接收类型int的值
int
c1 := make(chan int)           // int类型数据的非缓冲通道
c2 := make(chan int, 0)        // int类型数据的非缓冲通道
c3 := make(chan *os.File, 10)  // 文件指针类型的缓冲通道
<-
ch := make(chan int, 3)
ch <- 1                 // 向通道发送元素值
elem := <-ch            // 从通道接收元素值,并赋值给变量

2. Channel的接收和发送有哪些基本特性?

对于同一个channel,接收操作之间是互斥的,发送操作之间也是互斥的

  • 对于通道中的同一个值,接收操作和发送操作是互斥的
  • 在同一时刻,runtime只会执行对同一个通道的任意接收操作中的某一个,发送操作同理
  • 发送元素值的时候,外界元素会被复制,进入通道的是元素的副本,直到元素值被完全复制进入通道,其他对通道的发送操作才能执行
  • 接收元素值的时候,通道中的元素会被复制,生成副本,再准备给接收放,然后删除通道中的元素值,直到元素值被完全移除掉,其他对通道的接收操作才能执行

接收和发送操作对元素值的操作都是原子性的,不可分割:

  • 处理元素值的过程是不可打断的。发送操作要么没有复制数据,要么复制数据完毕,只有这两种状态,不会出现只复制一部分的中间状态;接收操作也是,绝不会出现元素值残留在通道的情况
  • 这个特性保证了元素值的完整性,确保了操作的唯一性,对于一个元素值来说,它只能被某一个发送操作放入通道中,也只能被某一个接收操作从通道中取出

发送操作和接收操作在完成之前会被阻塞:

  • 发送操作有两个步骤:「复制元素值」、「将副本放入通道」,在这两个步骤完成前,发送操作所在的goroutine会被阻塞
  • 接收操作有三个步骤:「复制通道中的元素」、「移动副本到接收方」、「删除原值」,接收操作所在的goroutine会被阻塞
  • 直到所有操作完成,操作所在goroutine会接收到runtime的通知,并获得运行机会
  • 阻塞就是为了实现操作的互斥性和保证元素完整性。

3. 缓冲通道和非缓冲通道的区别?

缓冲通道:

  • 当通道已满,所有对它的发送操作都会被阻塞,直到通道中有元素被取走
  • 由于通道内有发送等待队列,被阻塞发送操作所在的goroutine会按顺序进入等待队列,所以最早等待的goroutine会被优先通知
  • 当通道已空,所有对它的接收操作都会被阻塞,直到通道中有新元素放入
  • 由于通道内有接收等待队列,因此而等待的goroutine都会按顺序进入等待队列,最早等待的goroutine会被最先通知
  • 大多数情况下,通道作为收发双方的中间件,元素值会先从发送方复制到缓冲通道,再从缓冲通道复制接收方。到但是当发送操作执行时,发现空的通道中刚好有等待的接收操作,会直接把元素值复制给接收方
  • 缓冲通道使用异步方式传递数据

非缓冲通道:

  • 无论是接收操作还是发送操作,一开始执行就会被阻塞,直到另一方配对操作开始执行,才会传递数据,也就是通道两侧接收方和发送方同时存在并进行操作,才会进行数据传输
  • 数据是直接从发送方复制到接收方的,没有使用通道做中转
  • 非缓冲通道使用异步的方式传递数据
ch1 := make(chan int, 1)
ch1 <- 1
ch1 <- 2                  // 通道已满,操作阻塞

ch2 := make(chan int, 1)
elem, ok := <-ch2  //通道为空,操作阻塞

边界性:

通道有边界性,缓冲通道和非缓冲通道可以通过边界性理解为一种形式。通道有两个边界,一个是通道已满,阻塞发送操作;一个是通道已空,阻塞接收操作。非缓冲通道可以理解成是一个一直处于通道已满状态的缓冲通道。

4. 由于错误使用通道导致阻塞的情况?

对值为nil的通道进行发送操作或接收操作多会被阻塞,操作所属的goroutine都不会继续执行。

var ch chan int
ch <- 1          // 阻塞
<-ch             // 阻塞

5. 在什么情况下,发送操作和接收操作会发生panic?

  • 通道关闭,对其进行发送操作会引发panic
  • 关闭已经关闭的通道会引发panic
  • 接收操作可以感知到通道关闭,能够安全退出
elem, ok := <-ch  // 第二个变量ok为bool类型,如果ok为true,表示通道已关闭,并且再没有元素可取
  • 用第二个变量来判断通道是否关闭会有延迟,因为当通道已经关闭,但是通道里还有元素未取出时,第二个变量还会为true,并且元素可以取出

6. 通道的长度代表什么?它在什么时候与通道容量相同?

通道的长度代表通道当前含有的元素个数,在通道已满的状态下,会与容量相同

7. 元素在通道中传递会进行复制,该复制是深度拷贝吗?

不是深度拷贝,在Go中没有深度拷贝

8. 通道分为双向通道和单向通道,单向通道有什么作用?

双向通道既能发又能收