并发编程 2 (Channel)
1. 管道类型与初始化
//首先:channel是 Go 语言中一种特有的类型。声明通道如下所示:
var 变量名称 chan 元素类型
var ch1 chan int // 声明一个传递整型的通道
var ch2 chan bool // 声明一个传递布尔型的通道
var ch3 chan []int // 声明一个传递int切片的通道
var ch chan int //channel初值是nil
fmt.Println(ch) // <nil>
//初始化channel只能使用make函数实现:
make(chan 元素类型, [缓冲区大小])
ch4 := make(chan int)//不写就是无缓冲区(意义不大,一般还是会写缓存区大小的)
ch5 := make(chan bool, 1) // 声明一个缓冲区大小为1的通道
2. 管道操作
总结
通道共有发送(send)、接收(receive)和关闭(close)三种操作。而发送和接收操作都使用<-符号。
//定义一个通道:
ch := make(chan int)
//将一个值发送到通道中。
ch <- 10 // 把10发送到ch中
//从一个通道中接收值。
target:==<-ch
fmt.Println(target)
// 忽略结果的取值
<-ch
//批量接收值
//通常我们会选择使用for range循环从通道中接收值,当通道被关闭后,会在通道内的所有值被接收完毕后会自动退出循环。上面那个示例我们使用for range改写后会很简洁。
func f3(ch chan int) {
for v := range ch {
fmt.Println(v)
}
}
//关闭通道
close(ch)
//关闭通道可能的问题
1. 对一个关闭的通道再发送值就会导致 panic。
2. 对一个关闭的通道进行接收会一直获取值直到通道为空。
3. 对一个关闭的并且没有值的通道执行接收操作会得到对应类型的零值。
4. 关闭一个已经关闭的通道会导致 panic。
3. 批量接收数据
func f2(ch chan int) {
for {
v, ok := <-ch
if !ok {
fmt.Println("通道已关闭")
break
}
fmt.Printf("v:%#v ok:%#v\n", v, ok)
}
}
func main() {
ch := make(chan int, 2)
ch <- 1
ch <- 2
close(ch)
f2(ch)
}
//自己手写的批量接收值
func f3(ch chan int) {
for v := range ch {
fmt.Println(v)
}
}
//利用for range实现,(主要用这种)
4. 特殊管道
4.1 单向管道
总的来说
在函数参数中使用这样的类型即可,实现通道的单向
<- chan int // 只接收通道,只能接收不能发送
chan <- int // 只发送通道,只能发送不能接收
4.2 无缓冲通道(同步通道)
5. 与线程协作实现并发工作(小案例)
6. Select 多路复用
概念
在某些场景下我们可能需要同时从多个通道接收数据。通道在接收数据时,如果没有数据可以被接收那么当前 goroutine 将会发生阻塞
Go 语言内置了 select 关键字,使用它可以同时响应多个通道的操作。
Select 的使用方式类似于之前的 switch 语句,它也有一系列 case 分支和一个默认的分支。
每个 case 分支会对应一个通道的通信(接收或发送)过程。select 会一直等待,直到其中的某个 case 的通信操作完成时,就会执行该 case 分支对应的语句。
特殊在于多个 case 满足时,会随机选择一个,而不是按顺序
select {
case <-ch1:
//...
case data := <-ch2:
//...
case ch3 <- 10:
//...
default:
//默认操作
}
小案例与结果
package main
import "fmt"
func main() {
ch := make(chan int, 1)
for i := 1; i <= 10; i++ {
select {
case x := <-ch:
fmt.Println(x)
case ch <- i:
}
}
}
//结果可能值:
1
3
5
7
9