抱歉,您的瀏覽器無法訪問本站
本頁面需要瀏覽器支持(啟用)JavaScript
了解詳情 >

前言

Go中channel是一个核心类型,用于协程直接的通信以实现并发通信。

Go中对于并发编程有个核心思想:不要通过共享内存来通信,而是通过通信来共享内存,channel即是这一思想的实现。

简单使用

创建channel时需要指定一个传输类型,同时channel可以是单向或者双向。

  • chan T表示一个数据类型为T的双向通道类型,它可以接受和发送数据。
  • chan <- T表示一个数据类型为T的单向发送通道类型,它只可以发送数据。
  • <- chan T表示一个数据类型为T的单向接受通道类型,它只可以接受数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 创建无缓冲通道
ch1 := make(chan int)

// 创建缓存区大小为10的通道
ch2 := make(chan int, 10)

// 向通道ch1发送数据
ch1 <- 5

// 接受通道ch1的数据并赋值给i变量
i:= <-ch

// 查询通道缓冲区大小
cap(ch1)

// 查询该通道缓冲区中还有多少未接受的元素数量
len(ch1)

// 关闭通道,如果关闭一个nil通道或者已经关闭的通道会导致panic
close(ch1)

// 使用for range 读取chan
for i := range ch1{
fmt.Println(i)
}

// 使用select监控多个chan,只会处理非堵塞case
// 1. 当chan为nil则对应case永久堵塞
// 2. 当chan已被close则对应case不会堵塞
// 3. 当多个chan都为非堵塞,则随机选择一个
for{
select {
case <-ch1:
Todo1()
case <-ch2:
Todo2()
}
}

底层数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
type hchan struct {
qcount uint // 循环数组的元素数量
dataqsiz uint // 循环数组的长度
buf unsafe.Pointer // 指向循环数据的指针
elemsize uint16 // 缓冲区大小
closed uint32 // 是否已经关闭
elemtype *_type // 传输的元素类型
sendx uint // 下次循环数组读取的下标
recvx uint // 下次循环数组写入的下标
recvq waitq // 接受等待队列
sendq waitq // 发送等待队列
lock mutex // 互斥锁
}

例如下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func testChan() {
ch := make(chan int, 10)
waitGroup := sync.WaitGroup{}
waitGroup.Add(2)
go func() {
defer waitGroup.Done()
for i := 0; i < 10; i++ {
ch <- i
}
close(ch)
}()

go func() {
defer waitGroup.Done()
for i := range ch {
fmt.Println(i)
}
}()
waitGroup.Wait()
}

当我们往ch通道写入数据时,可能会有以下几种情况

  1. buf循环数组还有空闲空间并且recvq接受队列为空,那么便使用lock互斥锁进行加锁,将数据复制到buf中,将sendx++,随后释放互斥锁。
  2. buf循环数组为空同时recvq接受队列有消费协程堵塞住,那么并不会加锁和复制到buf中,而直接将数据复制到消费协程待接受的变量地址
  3. buf循环数组已满同时recvq接受队列为空,那么将当前协程放入sendq发送队列中,并堵塞当前协程。