管道
为了保持主线程不挂掉, 我们都会在最后写上一个死循环或者写上一个定时器来实现等待 goroutine 执行完毕。
上述实现并发的代码中为了解决生产者消费者资源同步问题, 我们利用加锁来解决, 但是这仅仅是一对一的情况, 如果是一对多或者多对多, 上述代码还是会出现问题。
综上所述, 企业开发中需要一种更牛 X 的技术来解决上述问题, 那就是管道(Channel)。
管道的本质是一个队列(先进先出)。
管道的线程是安全的,也就是自带锁定功能。
声明:var 变量名 chan 类型
初始化:mych:=make(chan 类型,容量)
channel 和切片、字典一样,必须 make 后才能使用,也都是应用类型。
注意点:
- 管道中只能存放声明的数据类型, 不能存放其它数据类型。
- 管道中如果已经没有数据, 再取就会报错。
- 如果管道中数据已满, 再写入就会报错。
管道的关闭和遍历
package main
import "fmt"
func main() {
// 1.创建一个管道
mych := make(chan int, 3)
// 2.往管道中存入数据
mych <- 666
mych <- 777
mych <- 888
// 3.遍历管道
//for i:=0; i<len(mych); i++{
// fmt.Println(<-mych) // 这种的输出结果不正确,因为长度在一直变化
//}
// 3.写入完数据之后先关闭管道
// 注意点: 管道关闭之后只能读不能写
close(mych)
//mych<- 999 // 报错
// 4.遍历管道
// 利用for range遍历, 必须先关闭管道, 否则会报错(造成死锁)
//for value := range mych{
// fmt.Println(value)
//}
// close主要用途:
// 在企业开发中我们可能不确定管道有还没有有数据, 所以我们可能一直获取
// 但是我们可以通过ok-idiom模式判断管道是否关闭, 如果关闭会返回false给ok(前提是已经关闭的channel)
for {
if num, ok := <-mych; ok {
fmt.Println(num)
} else {
break
}
}
fmt.Println("数据读取完毕")
}
Channel 阻塞现象
- 单独在主线程中操作管道, 写满了会报错, 没有数据去获取也会报错。
- 只要在协程中操作管道过, 写满了就会阻塞, 没有就数据去获取也会阻塞。
无缓冲 Channel 和有缓冲 Channel
- 有缓冲管道具备异步的能力(写几个读一个或读几个)
- 无缓冲管道具备同步的能力(写一个读一个)
单向管道和双向管道
- 默认情况下所有管道都是双向了(可读可写)
- 但是在企业开发中, 我们经常需要用到将一个管道作为参数传递
- 在传递的过程中希望对方只能单向使用, 要么只能写,要么只能读
- 双向管道
- var myCh chan int = make(chan int, 0)
- 单向管道
- var myCh chan<- int = make(chan<- int, 0)
- var myCh <-chan int = make(<-chan int, 0)
- 注意点:
- 双向管道可以自动转换为任意一种单向管道。
- 单向管道不能转换为双向管道。
package main
import "fmt"
func main() {
// 1.定义一个双向管道
var myCh chan int = make(chan int, 5)
// 2.将双向管道转换单向管道
var myCh2 chan<- int
myCh2 = myCh
fmt.Println(myCh2)
var myCh3 <-chan int
myCh3 = myCh
fmt.Println(myCh3)
// 3.双向管道,可读可写
myCh <- 1
myCh <- 2
myCh <- 3
fmt.Println(<-myCh)
// 3.只写管道,只能写, 不能读
// myCh2<-666
// fmt.Println(<-myCh2)
// 4.指读管道, 只能读,不能写
fmt.Println(<-myCh3)
//myCh3<-666
// 注意点: 管道之间赋值是地址传递, 以上三个管道底层指向相同容器
}