(1)channel 可以声明为只读,或者只写性质
(2)channel 只读和只写的最佳实践案例
在默认情况下,管道是双向管道,即可读可写。
var ch chan int
func main() {
//声明为只写管道
var chan1 chan<- int
chan1 = make(chan int, 3)
fmt.Println(chan1)
//声明为只读
var chan2 <-chan int
chan2 = make(chan int, 3)
fmt.Println(chan2)
}
下面是只读,只写管道,可以有效的预防我们误操作。相当于将可读可写管道将只读的功能封装起来了,不给你使用,但是类型依然是chan int类型。
select + channel 示例
使用select可以解决从管道取数据的阻塞问题【案例演示】
假设要从网上下载一个文件,我启动了 3 个 goroutine 进行下载,并把结果发送到 3 个 channel 中。其中,哪个先下载好,就会使用哪个 channel 的结果。
在这种情况下,如果我们尝试获取第一个 channel 的结果,程序就会被阻塞,无法获取剩下两个 channel 的结果,也无法判断哪个先下载好。这个时候就需要用到多路复用操作了,在 Go 语言中,通过 select 语句可以实现多路复用,其语句格式如下:
select {
case i1 = <-c1:
//todo
case c2 <- i2:
//todo
default:
// default todo
}
整体结构和 switch 非常像,都有 case 和 default,只不过 select 的 case 是一个个可以操作的 channel。
小提示:多路复用可以简单地理解为,N 个 channel 中,任意一个 channel 有数据产生,select 都可以监听到,然后执行相应的分支,接收数据并处理。
有了 select 语句,就可以实现下载的例子了,如下面的代码所示:
func main() {
//声明三个存放结果的channel
firstCh := make(chan string)
secondCh := make(chan string)
threeCh := make(chan string)
//同时开启3个goroutine下载
go func() {
firstCh <- downloadFile("firstCh")
}()
go func() {
secondCh <- downloadFile("secondCh")
}()
go func() {
threeCh <- downloadFile("threeCh")
}()
//开始select多路复用,哪个channel能获取到值,就说明哪个最先下载好,就用哪个。
select {
case filePath := <-firstCh:
fmt.Println(filePath)
case filePath := <-secondCh:
fmt.Println(filePath)
case filePath := <-threeCh:
fmt.Println(filePath)
}
}
func downloadFile(chanName string) string {
//模拟下载文件,可以自己随机time.Sleep点时间试试
time.Sleep(time.Second)
return chanName+":filePath"
}
如果这些 case 中有一个可以执行,select 语句会选择该 case 执行,如果同时有多个 case 可以被执行,则随机选择一个,这样每个 case 都有平等的被执行的机会。如果一个 select 没有任何 case,那么它会一直等待下去。
---------------------------------------------------------------------------------------------------------------------------
要从管道当中读取数据需要close这个管道,不去close这个管道去遍历的时候,管道会阻塞,如果阻塞在main协程里面会发生死锁。
package main
import (
"fmt"
"time"
)
func main() {
//使用select可以解决从管道当中读取数据阻塞的问题
//1.定义一个管道 10个数据int
initChan := make(chan int, 10)
for i := 0; i < 5; i++ {
initChan <- i
}
//2.定义一个管道 5个数据string
stringChan := make(chan string, 5)
for i := 0; i < 5; i++ {
stringChan <- "hello" + fmt.Sprintf("%d", i)
}
//传统的方法在遍历管道的时候如果不关闭会阻塞导致死锁
//在实际开发过程中,可能无法确定什么时候关闭该管道
//可以使用select的方式可以解决
for {
select {
//如果管道一直没有关闭,也不会一直阻塞在这里导致死锁
// 它会自动的到下一个case匹配,不依赖关闭
case v := <-initChan:
time.Sleep(time.Second * 1)
fmt.Printf("从intchan读取了数据%d\n", v)
case v := <-stringChan:
time.Sleep(time.Second * 1)
fmt.Printf("从stringChan读取了数据%s\n", v)
default:
time.Sleep(time.Second * 1)
fmt.Println("都取不到了,不玩了,这里可以加入业务逻辑")
}
}
}
从intchan读取了数据0
从intchan读取了数据1
从intchan读取了数据2
从intchan读取了数据3
从intchan读取了数据4
从stringChan读取了数据hello0
从stringChan读取了数据hello1
从stringChan读取了数据hello2
从stringChan读取了数据hello3
从stringChan读取了数据hello4
都取不到了,不玩了,这里可以加入业务逻辑
都取不到了,不玩了,这里可以加入业务逻辑
都取不到了,不玩了,这里可以加入业务逻辑
都取不到了,不玩了,这里可以加入业务逻辑
如果是在函数里面,直接使用return,那么就直接退出协程的函数。
4)goroutine中使用recover,解决协程中出现panic,导致程序崩溃问题.【案例演示】
说明:如果我们起了一个协程,但是这个协程出现了panic,如果我们没有捕获这个panic,就会造成整个程序崩溃,这时我们可以在goroutine中使用recover来捕获panic,进行处理,这样即使这个协程发生的问题,但是主var mapl map[string]string线程仍然不受影响,可以继续执行。map1["nol”]="tom"