channel的基本介绍
- channel的本质是一个数据结构队列
- 数据是先进先出 FIFO
- 线程安全,多goroutine访问时,不需要加锁,就是说channel本身是线程安全的
- channel是由类型的,一个string的channel只能存放string类型数据
- 无缓冲的channel关闭后,再往外读数据读到的是该管道数据类型的初始值
- 有缓冲的channel的channel关闭后,如果管道内还有未被读出来的数据,可以继续读出来
判断管道是否关闭
if num,ok := <-ch;ok{
//如果ok为false,定是管道已经关闭了
}
使用for range则无需关心关闭的细节,go已经帮我们做好了,一旦检测到关闭,range会自己停止,但是程序的某一处一定要有关闭管道的操作,否则会报死锁
双向channel可以赋值给单项channel,反过来则不行
package main
import (
"fmt"
)
func send(out chan<- int) {
out <- 89
close(out)
}
func recv(in <-chan int) {
n := <-in
fmt.Println("读到", n)
}
func main() {
ch := make(chan int)
go func() {
send(ch)
}()
recv(ch)
}
定义/声明
var intChan chan int //intChan用于存放int数据
var mapChan chan map[int]string //mapChan用于存放map[int]string类型
var perChan chan Person
var perChan2 chan *Person
- channel是引用类型
- channel必须初始化才能写入数据,即make后才能使用
- 管道是由类型的,intChan只能写入整数int
package main
import "fmt"
func main(){
var intChan chan int
intChan = make(chan int, 3)
fmt.Printf("intChan的值=%v intChan本身的地址=%p\n",intChan, &intChan)
//intChan的值=0xc00007a080 intChan本身的地址=0xc000006028
intChan<- 10
num:=211
//向管道写入数据
intChan<- num
//看看管道的长度和cap(容量)
fmt.Printf("channel len=%v cap=%v \n",len(intChan),cap(intChan))
//从管道中读取数据
var num2 int
num2 = <-intChan
fmt.Println("num2=", num2)
fmt.Printf("channel len=%v cap=%v \n",len(intChan),cap(intChan))
<-intChan //直接取值不接收
//在没有下,取完后继续取回报错
}
如果使用空interface类型的管道,取出来的结构体数据是 接口类型,需要类型断言才能使用
package main
import "fmt"
type Cat struct{ Name string }
func main() {
var catChan chan interface{}
catChan = make(chan interface{}, 10)
cat := Cat{"小花猫"}
catChan <- cat
newCat := <-catChan //newCat.Name是错的编译的时候编译器会认为newCat是一个接口
c := newCat.(Cat) //使用类型断言转换之后可以正常使用
fmt.Println(c.Name) //小花猫
}
channel的关闭
- channel一旦关闭只能读,不能写
package main
import "fmt"
func main(){
intChan := make(chan int, 3)
intChan<- 100
intChan<- 200
close(intChan) //close channel此时只能读不能写
}
channel的遍历
package main
import "fmt"
func main() {
intChan := make(chan int, 100)
for i := 0; i < 100; i++ {
intChan <- i * 2 //放入100个数据到管段
}
//遍历管道要使用fo range的方式去遍历,普通遍历不可以,因为每取一次容量会减少
close(intChan) // 在遍历时,如果管道没有关闭,则会出现deadlock的错误
for v := range intChan {
fmt.Println("v=", v)
}
}
channel支持val,ok:= <-intChan这种方法
intChan := make(chan int, 100)
for i := 0; i < 100; i++ {
intChan <- i * 2 //放入100个数据到管段
}
if v, ok := <- intChan;ok{ //成功取到值ok为true否则为false
fmt.Print(v)
}
管道阻塞的机制
如果编译器运行,发现一个管道只有写,没有读,则该管道会阻塞
写管道和读管道的频率不一致,无所谓。
判断管道是否关闭
for{
if v, isClose := <- intChan;isClose{ //通过这种方式判断管道已经关闭
break
}
}
只读和只写类型的管道
var wChan chan <-int
var rChan <-chan int
传统的方法在遍历管道时,如果不关闭会阻塞而导致deadlock,而使用select可以解决从管道取数据的阻塞问题
select 的用法有点类似 switch 语句,但 select 不会有输入值而且只用于信道操作。select 用于从多个发送或接收信道操作中进行选择,语句会阻塞直到其中有信道可以操作,如果有多个信道可以操作,会随机选择其中一个 case 执行。
看下例子
func service1(ch chan string) {
time.Sleep(2 * time.Second)
ch <- "from service1"
}
func service2(ch chan string) {
time.Sleep(1 * time.Second)
ch <- "from service2"
}
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go service1(ch1)
go service2(ch2)
select { // 会发送阻塞
case s1 := <-ch1:
fmt.Println(s1)
case s2 := <-ch2:
fmt.Println(s2)
}
}
输出:from service2 上面的例子执行到 select 语句的时候回发生阻塞,main 协程等待一个 case 操作可执行,很明显是 service2 先准备好读取的数据(休眠 1s),所以输出 from service2。 看下在两种操作都准备好的情况:
func service1(ch chan string) {
//time.Sleep(2 * time.Second)
ch <- "from service1"
}
func service2(ch chan string) {
//time.Sleep(1 * time.Second)
ch <- "from service2"
}
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go service1(ch1)
go service2(ch2)
time.Sleep(2*time.Second)
select {
case s1 := <-ch1:
fmt.Println(s1)
case s2 := <-ch2:
fmt.Println(s2)
}
}
//我们把函数里的延时注释掉,主函数 select 之前加 2s 的延时以等待两个信道的数据准备好,select 会随机选取其中一个 case 执行,所以输出也是随机的。
与 switch 语句类似,select 也有 default case,是的 select 语句不在阻塞,如果其他信道操作还没有准备好,将会直接执行 default 分支。
func service1(ch chan string) {
ch <- "from service1"
}
func service2(ch chan string) {
ch <- "from service2"
}
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go service1(ch1)
go service2(ch2)
select { // ch1 ch2 都还没有准备好,直接执行 default 分支
case s1 := <-ch1:
fmt.Println(s1)
case s2 := <-ch2:
fmt.Println(s2)
default:
fmt.Println("no case ok")
}
}
添加超时时间
有时候,我们不希望立即执行 default 语句,而是希望等待一段时间,若这个时间段内还没有可操作的信道,则执行规定的语句。可以在 case 语句后面设置超时时间。
func service1(ch chan string) {
time.Sleep(5 * time.Second)
ch <- "from service1"
}
func service2(ch chan string) {
time.Sleep(3 * time.Second)
ch <- "from service2"
}
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go service1(ch1)
go service2(ch2)
select { // 会发送阻塞
case s1 := <-ch1:
fmt.Println(s1)
case s2 := <-ch2:
fmt.Println(s2)
case <-time.After(2*time.Second): // 等待 2s
fmt.Println("no case ok")
}
}
goroutine中使用recover,必须定义在被协程调用的函数中才行,在main函数中捕获不到
func test(){
defer func(){
if err := recover(); err!=nil{
fmt.Println("test() 发生错误",err)
}
}()
var myMap map[int]string
myMap[0] = "golang" //error 这里没有make直接使用map会抛出异常
}