Go的通道、管道类型(chan)

Go语言的通道、管道类型(chan)

chan通道、管道类型的数据结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 源码来自Go 1.19
// 源码包:src/runtime/chan.go
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 protects all fields in hchan, as well as several
// fields in sudogs blocked on this channel.
//
// Do not change another G's status while holding this lock
// (in particular, do not ready a G), as this can deadlock
// with stack shrinking.
lock mutex // 互斥锁、保证chan并发安全操作
}

通道、管道类型是Go在语言层面提供的协程间的通信方式、也可以用于并发控制;

Go语言的通道、管道类型实现互斥锁

1
2
3
4
5
6
7
8
9
10
var counter int = 0
var ch = make(chan int, 1)

func Work() {
ch <- 1 // 写入
counter++
<-ch // 读取
}

// 只有一个缓冲区的通道、写入数据类似于加锁、读取数据类似释放锁

通道、管道数据读写

通道、管道没有缓冲区时,从管道读取数据会阻塞,直到有协程向管道中写入数据;
类似的,向通道、管道写入数据也会阻塞,直到有协程从管道读取数据;

内置函数len()查询通道、管道缓冲区中数据的个数;
内置函数cap()查询缓冲区的大小;

1
2
3
4
5
6
7
8
9
func myChan() {
ch := make(chan int, 10)

ch <-1
ch <-2

fmt.Println(len(ch)) // 2
fmt.Println(cap(ch)) // 10
}

通道、管道实现了一种FIFO(先入先出)的队列、数据总是按照写入的顺序流出管道;

单项管道的使用例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func readChan(chanName <-chan int) {
data:= <- chanName
fmt.Println(data)
}

func writeChan(chanName chan<- int) {
chanName <- 1
}

func main() {
var mychan = make(chan int, 10)

writeChan(mychan)
readChan(mychan)
}

通道、管道 + select监控多个协程的执行情况

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
package main

import (
"fmt"
"time"
)

func main() {
var ch1 = make(chan int, 10)
var ch2 = make(chan int, 10)

go work(ch1)
go work(ch2)

for{
select{
case e:=<-ch1:
fmt.Printf("get element from ch1:%d\n", e)
case e:=<-ch2:
fmt.Printf("get element from ch2:%d\n", e)
default:
fmt.Println("no element in ch1 and ch2!")
time.Sleep(time.Second * 1)
}
}
}

func work(ch chan int) {
for{
ch <-1
time.Sleep(time.Second * 1)
}
}

select在main中监控多个协程的执行情况;case的执行顺序是随机的(伪随机算法)实现I/O多路复用;