🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
> # channel - 长度和容量 ~~~ package main import "fmt" func main() { ch := make(chan struct{}, 3) ch <- struct{}{} fmt.Println(len(ch)) //输出1 fmt.Println(cap(ch)) //输出3 } ~~~ - 限制发送/接收类型 ~~~ chan T // 可以接收和发送类型为 T 的数据 chan<- T // 只可以用来发送 T 类型的数据 <-chan T // 只可以用来接收 T 类型的数据 ~~~ > # 底层数据结构 ``` type hchan struct { qcount uint // 通道中的当前元素个数 dataqsiz uint // 缓冲区的大小 (无缓冲时为0) buf unsafe.Pointer // *环形缓冲区* 指向缓冲区的指针 ,缓冲区通过这个指针来存储数据,已经发送但还未被接收的数据 elemsize uint16 // 单个元素的大小(如果通道是 `chan int` 类型,那么每个元素就是一个 `int`,`elemsize` 表示 `int` 类型的字节大小。) closed uint32 // 标识 channel 是否已关闭 (0 表示未关闭,1 表示已关闭) timer *timer //用于处理与该通道相关的超时操作 elemtype *_type //指向类型描述符的指针,它包含了通道中传输数据类型的所有信息 sendx uint // 下一个要发送元素的位置 recvx uint // 下一个要接收元素的位置 (每次读取数据后,`recvx` 都会递增,当到达缓冲区末尾时,`recvx` 会重置为 0,形成环形读取操作) recvq waitq // 等待接收数据的 goroutine 队列 sendq waitq // 等待发送数据的 goroutine 队列 lock mutex // 互斥锁,用于保护 channel 的操作 } ``` > # 发送和接收数据的本质 - 向 channel 发送值类型会拷贝, 发送引用类型拷贝的是引用 ~~~ package main import "fmt" func main() { ch := make(chan []int, 1) s := make([]int, 1) s[0] = 1 ch <- s s[0] = 2 fmt.Println(<-ch) //输出2 } ~~~ > # 操作 nil channel, close channel, 正常 channel | 操作 | nil channel | close channel | 正常 channel | | --- | --- |--- |--- | | close | panic: close of nil channel | panic: close of closed channel | 正常关闭| | 读操作 | fatal error: all goroutines are asleep - deadlock! | 有未接收的值可以正常读, 没有的话读到对应类型的零值| 阻塞/正常读数据| | 写操作 | fatal error: all goroutines are asleep - deadlock! | panic: send on closed channel | 阻塞/正常写数据| > # 使用 select 来多路复用 channel - **随机选择**:当多个 `channel` 同时满足条件时,`select` 会随机选择一个执行。 - **默认分支**:可以在 `select` 中添加 `default` 分支,当所有的 `channel` 都没有数据时,`select` 可以立即执行 `default` 分支而不阻塞。 ~~~ package main import ( "fmt" "time" ) func main() { ch1 := make(chan struct{}) ch2 := make(chan struct{}) go func() { ch1 <- struct{}{} }() go func() { ch2 <- struct{}{} }() select { case <-ch1: fmt.Println("ch1") case <-ch2: fmt.Println("ch2") case <-time.After(2 * time.Second): fmt.Println("超时") default: fmt.Println("default") time.Sleep(1 * time.Second) } } ~~~ > # range channel ``` package main import ( "fmt" ) func main() { ch := make(chan int) go func() { close(ch) }() //如果通道不被关闭,range 将会一直等待新的数据,不会自动退出循环 for v := range ch { fmt.Println(v) } fmt.Println("Done") } ``` > # 读取关闭的channel ~~~ package main import "fmt" func main() { ch := make(chan int, 2) ch <- 1 ch <- 2 close(ch) v, ok := <-ch fmt.Println(v, ok) //1 true v, ok = <-ch fmt.Println(v, ok) //2 true v, ok = <-ch fmt.Println(v, ok) //0 false //没数据了,返回对应类型的零值和false } ~~~ > # 如何优雅的关闭 channel - v, ok := <-ch 取值的时候加判断, 关闭的channel, v 返回对应类型的零值, ok 返回 false。用这种方式去判断channel 是否关闭有副作用, 会读出channel里的元素 - [如何优雅的关闭Go Channel](https://www.ulovecode.com/2020/07/14/Go/Golang%E8%AF%91%E6%96%87/%E5%A6%82%E4%BD%95%E4%BC%98%E9%9B%85%E5%85%B3%E9%97%ADGo-Channel/) - 多生产者多消费者例子(关闭原则:不要在消费端关闭channel,不要在生产端有多个的并行时候执行关闭操作) ~~~ package main import ( "fmt" "sync" "time" ) func main() { ch := make(chan int, 1024) chClose := make(chan struct{}) var wg sync.WaitGroup wg.Add(10) for i := 0; i < 10; i++ { go func(num int) { for { select { case <-chClose: fmt.Println("发送关闭1", num) return default: } select { case <-chClose: fmt.Println("发送关闭1", num) return case ch <- num: } } }(i) } for i := 0; i < 10; i++ { go func(num int) { for { select { case <-chClose: fmt.Println("接收关闭1", num) wg.Done() return default: } select { case <-chClose: fmt.Println("接收关闭2", num) wg.Done() return case v, _ := <-ch: _ = v } } }(i) } time.Sleep(time.Second * 3) chClose <- struct{}{} close(chClose) wg.Wait() } ~~~ > # 交替打印 - 可以通过runtime.GOMAXPROCS设置处理器数量为1, runtime.Gosched() 让出当前调度 ~~~ package main import ( "fmt" "time" ) func main() { //不能是 ch := make(chan struct{}, 1), 协程调度是随机的, 如果缓存为1,会出现某个协程被执行多次 ch := make(chan struct{}, 0) go func() { for { <-ch fmt.Println(1) ch <- struct{}{} } }() go func() { for { <-ch fmt.Println(2) ch <- struct{}{} } }() ch <- struct{}{} time.Sleep(time.Hour) } ~~~