1.1.1. 一、使用channel (管道)的注意事项:
1、channel 可以声明为只读,或者只写。
默认情况下管道是双向的,但是管道声明的时候,可以声明为只读,或者只写。
- 声明只写管道:
var intChan chan<- int
func main() {
//只写管道 chan<-
var intChan chan<- int = make(chan int,3)
intChan<- 10
intChan<-20
//<-intChan //编译报错,只写管道,不能读取。
}
- 声明只读管道:
var intChan <-chan int
func main() {
//只读管道 <-chan
var intChan <-chan int = make(chan int,3)
//intChan<- 10 编译错误,只读管道,不能写。
}
还可以把只读,或只写的管道作为函数参数。
//只写管道intChan //只写管道 readChan func test(intChan chan<- int,readChan <-chan int){ }
2、使用 select 可以解决从管道取数据的阻塞问题。
传统的方法(for-range)在遍历管道取数据时,如果管道没有关闭,会导致死锁。传统方法在取没有关闭的管道的数据时,如果没有数据,再取,也会报错死锁。使用 select 可以解决这个问题。
- 使用select从管道取数据的语法:
select {
case v := <-intChan:
fmt.Printf("从 intChan 读取的数据%d \n", v)
case v := <-stringChan:
fmt.Printf("从 stringChan 读取的数据%v \n", v)
default:
fmt.Println("没有读取到数据。。。")
}
case 后面取管道的数据,即使管道一直不关闭,也不会一直阻塞,就不会导致死锁。
因为 第一个case取不到,执行下一个case,如果全部都没有,就执行default。
- 完整的例子如下:
// 声明管道intChan,并放入数据
intChan := make(chan int,3)
for i:=0; i<3; i++ {
intChan<- i
}
// 声明管道stringChan,并放入数据
stringChan := make(chan string,3)
for i:=0; i<3; i++ {
stringChan<- strconv.Itoa(i) + "stringChan"
}
// for循环取管道的数据。取数据的时候使用select
label:
for {
select {
case v := <-intChan: //管道一直不关闭,也不会一直阻塞,就不会导致死锁。
fmt.Printf("从 intChan 读取的数据%d \n", v)
case v := <-stringChan:
fmt.Printf("从 stringChan 读取的数据%v \n", v)
default:
fmt.Println("没有读取到数据。。。")
break label //跳到label,结束for循环
}
}
3、goroutine 中使用 recover,解决协程中出现 panic,导致程序崩溃问题。
如果开启了一个协程,但是这个协程中出现了panic错误。如果我们不捕获这个panic错误,就会造成整个程序崩溃。
这时候我们可以在goroutine 中使用 recover来捕获panic,进行处理。这样即使协程发生问题,但是主线程不受影响,可以继续执行下去。
// 正常代码
func test() {
for i := 0; i < 3; i++ {
fmt.Println("hello..",i)
}
}
// 捕获异常代码
func testErr() {
// defer 延迟执行
defer func() {
// 捕获异常
if err := recover(); err != nil{
fmt.Println("testErr() 发生错误", err)
}
}()
var myMap map[int]string
myMap[0] = "golang" //error,没有make分配空间
}
func main() {
go test()
go testErr()
time.Sleep(time.Second * 2)
fmt.Println("main 结束。。。。。。")
}
执行结果如下:
hello.. 0
hello.. 1
hello.. 2
testErr() 发生错误 assignment to entry in nil map
main 结束。。。。。。