跳到主要内容

channel

一 channel简介

如果在程序中开启了多个goroutine,那么这些goroutine之间该如何通信呢?

go提供了一个channel(管道)数据类型,可以解决协程之间的通信问题!channel的本质是一个队列,遵循先进先出规则(FIFO),内部实现了同步,确保了并发安全!

二 channel的创建

2.0 channel的语法

channel在创建时,可以设置一个可选参数:缓冲区容量

  • 创建有缓冲channel:make(chan int, 10),创建一个缓冲长度为10的channel
  • 创建无缓冲channel:make(chan int),其实就是第二个参数为0

channel内可以存储多种数据类型,如下所示:

	ci := make(chan int)
cs := make(chan string)
cf := make(chan interface{})

从管道中读取,或者向管道写入数据,使用运算符:<-,他在channel的左边则是读取,右边则代表写入:

	ch := make(chan int)
ch <- 10 // 写入数据10
num := <- ch // 读取数据

2.1 无缓冲channel

无缓冲的channel是阻塞读写的,必须写端与读端同时存在,写入一个数据,则能读出一个数据:

package main

import (
"fmt"
"time"
)

// 写端
func write(ch chan int) {
ch <- 100
fmt.Printf("ch addr:%v\n", ch) // 输出内存地址
ch <- 200
fmt.Printf("ch addr:%v\n", ch) // 输出内存地址
ch <- 300 // 该处数据未读取,后续操作直接阻塞
fmt.Printf("ch addr:%v\n", ch) // 没有输出
}

// 读端
func read(ch chan int) {
// 只读取两个数据
fmt.Printf("取出的数据data1:%v\n", <-ch) // 100
fmt.Printf("取出的数据data2:%v\n", <-ch) // 200
}

func main() {

var ch chan int // 声明一个无缓冲的channel
ch = make(chan int) // 初始化

// 向协程中写入数据
go write(ch)

// 向协程中读取数据
go read(ch)

// 防止主go程提前退出,导致其他协程未完成任务
time.Sleep(time.Second * 3)
}

注意:无缓冲通道的收发操作必须在不同的两个goroutine间进行,因为通道的数据在没有接收方处理时,数据发送方会持续阻塞,所以通道的接收必定在另外一个 goroutine 中进行。

如果不按照该规则使用,则会引起经典的Golang错误fatal error: all goroutines are asleep - deadlock!

func main() {
ch := make(chan int)
ch <- 10
<-ch
}

2.2 有缓存channel

有缓存的channel是非阻塞的,但是写满缓冲长度后,也会阻塞写入。

package main

import (
"fmt"
"time"
)

// 写端
func write(ch chan int) {
ch <- 100
fmt.Printf("ch addr:%v\n", ch) // 输出内存地址
ch <- 200
fmt.Printf("ch addr:%v\n", ch) // 输出内存地址
ch <- 300 // 写入第三个,造成阻塞
fmt.Printf("ch addr:%v\n", ch) // 没有输出
}
func main() {

var ch chan int // 声明一个有缓冲的channel
ch = make(chan int, 2) // 可以写入2个数据

// 向协程中写入数据
go write(ch)

// 防止主go程提前退出,导致其他协程未完成任务
time.Sleep(time.Second * 3)
}

同样的,当数据全部读取完毕后,再次读取也会造成阻塞,如下所示:

func main() {
ch := make(chan int, 1)
ch <- 10
<-ch
// <-ch
}

此时程序可以顺序运行,不会报错,这是与无缓冲通道的区别,但是当继续打开 注释 部分代码时,通道阻塞,所有协程挂起,此时也会产生错误:fatal error: all goroutines are asleep - deadlock!

2.3 总结 无缓冲通道与有缓冲通道

无缓冲channel:

  • 通道的容量为0,即 cap(ch) = 0
  • 通道的个数为0,即 len(ch) = 0
  • 可以让读、写两端具备并发同步的能力

有缓冲channel:

  • 在make创建的时候设置非0的容量值
  • 通道的个数为当前实际存储的数据个数
  • 缓冲区具备数据存储的能力,到达存储上限后才会阻塞,相当于具备了异步的能力
  • 有缓冲channel的阻塞产生条件:
    • 当缓冲通道被填满时,尝试再次发送数据会发生阻塞
    • 当缓冲通道为空时,尝试接收数据会发生阻塞

问题:为什么 Go语言对通道要限制长度而不提供无限长度的通道?

channel是在两个 goroutine 间通信的桥梁。使用 goroutine 的代码必然有一方提供数据,一方消费数据 。通道如果不限制长度,在生产速度大于消费速度时,内存将不断膨胀直到应用崩溃。