什么是通道?
goroutine
的通信管道
如何李姐通道
-
首先不要混淆
goroutine
和channel
之间的关系 -
goroutine
是并发的结构体 -
channel
是每个结构体通信的桥梁
channel
的特点:
channel
是一个通信机制,每个channel
都有一个特殊的类型,一个可以发送int
类型的channel
一般写为chan int
go
的并发特点:
使用通道代替共享内存
goroutine
和channel
关系:
多个goroutine
争抢数据会造成执行的低效率,使用队列的方式是最高效的。channel
是队列一样的结构
Go通道种类
单向通道
无缓冲通道
带缓冲通道
通道的特性
特点:
-
任何时候同时只能有一个
goroutine
访问通道进行发送和获取数据 -
遵循先入先出的规则(
First In First Out
)
声明通道类型(通道是一种类型)
通道的声明方式:
var 通道变量 chan 通道类型;
-
通道类型:通道内的数据类型。
-
通道变量:保存通道的变量。
chan
类型的空值是nil
,声明后需要make
才能使用
创建通道
通道的特点:
通道是引用类型,要使用make
进行创建。
声明通道实例的方式:
通道实例 := make(chan 数据类型)
-
数据类型:通道内传输的元素类型。
-
通道实例:通过
make
创建的通道句柄。
实例:
ch1 := make(chan int) // 存放int类型数据的通道
ch2 := make(chan interface{}) // 创建一个空接口类型的通道, 可以存放任意格式
type Equip struct{/* 一些字段 */}
ch2 := make(chan *Equip) // 创建Equip指针类型的通道, 可以存放*Equip
使用通道发送数据
通道发送数据的格式
通过通道发送数据的例子
发送将持续阻塞直到数据被接收
通道发送数据的格式
使用特殊操作符<-
,格式为:
通道变量 <- 值
箭头指向make
创建好的通道实例,将值传入通道实例当中
-
通道变量:通过
make
创建好的通道实例。 -
值:可以是变量、常量、表达式或者函数返回值等。值的类型必须与
ch
通道的元素类型一致。
通道发送数据的例子
make
创建通道实例,<-
向通道发送数据:
// 创建一个空接口通道
ch := make(chan interface{})
// 将int类型数据放入通道中
ch <- 0
// 将string类型的数据放入通道中
ch <- "Hello,World!"
通道内的数据如果没有被接收将持续阻塞
把数据往通道中发送时,如果接收方一直都没有接收,那么发送操作将持续阻塞。
package main
func main() {
// 创建一个通道
ch := make(chan int)
// 将一个值放入通道
ch <- 0
}
/*
结果将持续阻塞,执行后会报错:
fatal error: all goroutines are asleep - deadlock!
*/
使用通道接收数据
阻塞接收数据
非阻塞接收数据
接收任意数据,忽略接收的数据
循环接收
通道接收数据的特点:
-
使用操作符
<-
-
通道的手法操作在两个
goroutine
中进行,若通道没有接收方则会持续阻塞。 -
接收持续阻塞直到发送方发送数据。
-
每次接收一个元素。
阻塞接收数据
<
始终指向接收方
data := <-ch
/*
执行该语句时将会阻塞,直到接收到数据并赋值给 data 变量。
*/
非阻塞接收数据
格式:
data, ok := <-ch
-
data:表示接收到的数据。未接收到数据时,
data
为通道类型的零值。 -
ok:表示是否接收到数据。
接收任意数据,忽略接收的数据
阻塞接收数据,忽略从通道返回的数据:
<-ch
/*
特点:
1、执行语句会发生阻塞,直到接收到数据
2、接收到的数据会被忽略
*/
作用:
通过通道在goroutine
间阻塞收发实现并发同步
示例:
package main
import "fmt"
func main() {
// 构建一个通道--->int类型的
ch := make(chan int)
// 开启一个匿名函数--->实现往通道中写入数据的功能
go func() {
// 开启goroutine
fmt.Println("开启goroutine:")
// 向通道当中写入数据
ch <- 0
// 关闭goroutine
fmt.Println("关闭goroutine!")
}()
// 开始匿名等待
fmt.Println("等待goroutine!")
// 匿名等待
<-ch
/*
开启 goroutine 后,马上通过通道等待匿名 goroutine 结束。
*/
// 结束
fmt.Println("结束!")
}
分析:
<-ch
会忽略接收的内容。所以不会报死锁的错误。
循环接收
借用for range
语句进行多个元素接收:
for data := range ch {
}
/*
通道可遍历
*/
示例代码:
package main
import (
"fmt"
"time"
)
/*
遍历通道内容:
1、创建一个通道
2、循环往通道当中添加数据
3、使用for range循环读取通道当中的数据以及类型
*/
func main() {
// 构建一个通道
ch2 := make(chan int)
// 创建一个匿名goroutine循环向通道当中添加数据
go func() {
// 循环添加0->3
for i := 3; i >= 0 ; i-- {
// 向通道当中发送数据
ch2 <- i
// 发送一次等待一秒
time.Sleep(time.Second)
}
}()
// 通过循环遍历读取通道内的数据--->遍历不是接收,不会造成死锁
for data := range ch2 {
fmt.Println(data)
// 读到0跳出循环
if 0 == data {
break
}
}
}
/*
当接收到数值 0 时,停止接收。如果继续发送,由于接收 goroutine 已经退出,没有 goroutine 发送到通道,因此运行时将会触发宕机报错。
*/s