通道
- 单纯地将函数并发执行是没有意义的。函数与函数间需要交换数据才能体现并发执行函数的意义。
- 虽然可以使用共享内存进行数据交换,但是共享内存在不同的goroutine中容易发生竞态问题。为了保证数据交换的正确性,必须使用互斥量对内存进行加锁,这种做法势必造成性能问题。
- Go语言提倡使用通信的方法代替共享内存,这里通信的方法就是使用通道(channel)
- Go语言中的通道(channel)是一种特殊的类型。在任何时候,同时只能有一个goroutine访问通道进行发送和获取数据。goroutine间通过通道就可以通信。通道像一个传送带或者队列,总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序。
声明通道类型
声明格式如下:
var 通道变量 chan 通道类型
- 通道类型:通道内的数据类型。
- 通道变量:保存通道的变量。 chan类型的空值是nil,声明后需要配合make后才能使用。
创建通道
- 通道是引用类型,需要使用make进行创建,格式如下:
- 通道实例 := make(chan 数据类型)
- 类似缓存
ch1 := make(chan int) // 创建一个整型类型的通道
ch2 := make(chan interface{}) // 创建一个空接口类型的通道,可以存放任意格式
type Equip struct{ /* 一些字段 */ }
ch2 := make(chan *Equip) // 创建Equip指针类型的通道,可以存放*Equip
//1. 创建一个可以存放3 个int 类型的管道
var intChan chan int
intChan = make(chan int, 3)
通道发送
通道的发送使用特殊的操作符“<-”,将数据通过通道发送的格式为:
通道变量 <- 值
·通道变量:通过make创建好的通道实例。
·值:可以是变量、常量、表达式或者函数返回值等。值的类型必须与ch通道的元素类型一致。
- 使用make创建一个通道后,就可以使用“<-”向通道发送数据,代码如下:
// 创建一个空接口通道
ch := make(chan interface{})
// 将0放入通道中
ch <- 0
// 将hello字符串放入通道中
ch <- "hello"
使用通道接收数据
通道接收同样使用“<-”操作符,通道接收有如下特性:
- 通道的收发操作在不同的两个goroutine间进行。 由于通道的数据在没有接收方处理时,数据发送方会持续阻塞,因此通道的接收必定在另外一个goroutine中进行。
- 接收将持续阻塞直到发送方发送数据。 如果接收方接收时,通道中没有发送方发送数据,接收方也会发生阻塞,直到发送方发送数据为止。
- 每次接收一个元素。 通道一次只能接收一个数据元素。 通道的数据接收一共有以下4种写法。
- 1.阻塞接收数据 阻塞模式接收数据时,将接收变量作为“<-”操作符的左值,格式如下:
- data := <-ch
- 执行该语句时将会阻塞,直到接收到数据并赋值给data变量。
- 2.非阻塞接收数据
使用非阻塞方式从通道接收数据时,语句不会发生阻塞,格式如下:
data, ok := <-ch
- data:表示接收到的数据。未接收到数据时,data为通道类型的零值。
- ok:表示是否接收到数据。
- 非阻塞的通道接收方法可能造成高的CPU占用,因此使用非常少。如果需要实现接收超时检测,可以配合select和计时器channel进行,可以参见后面的内容。
- 3.接收任意数据,忽略接收的数据阻塞接收数据后,忽略从通道返回的数据,格式如下:<-ch 执行该语句时将会发生阻塞,直到接收到数据,但接收到的数据会被忽略。这个方式实际上只是通过通道在goroutine间阻塞收发实现并发同步。
- 4.循环接收
- 通道的数据接收可以借用for range语句进行多个元素的接收操作,格式如下: for data := range ch { }
- 通道ch是可以进行遍历的,遍历的结果就是接收到的数据。数据类型就是通道的数据类型。通过for遍历获得的变量只有一个,即上面例子中的data。
- 1.阻塞接收数据 阻塞模式接收数据时,将接收变量作为“<-”操作符的左值,格式如下:
- channel 中只能存放指定的数据类型
- channle 的数据放满后,就不能再放入了
- 如果从channel 取出数据后,可以继续放入
- 在没有使用协程的情况下,如果channel 数据取完了,再取,就会报dead lock