一 channel 可以声明为只读,或者只写性质
1 代码
package main
import (
"fmt"
)
func main() {
// 管道可以声明为只读或者只写
// 1 在默认情况下,管道是双向
// var chan1 chan int // 可读可写
// 2 声明为只写
var chan2 chan<- int
chan2 = make(chan int, 3)
chan2 <- 20
// num := <-chan2 // 读发生错误
fmt.Println("chan2=", chan2)
// 3 声明为只读
var chan3 <-chan int
num2 := <-chan3
// chan3<- 30 // 写发生错误
fmt.Println("num2", num2)
}
2 测试
chan2= 0xc042078080
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive (nil chan)]:
main.main()
E:/gocode/src/chapter16/channeldetails/demo01/main.go:19 +0x106
二 channel 只读或只写的最佳实践
1 代码
package main
import "fmt"
func main() {
var ch chan int
ch = make(chan int, 10)
exitChan := make(chan struct{}, 2)
go send(ch, exitChan)
go recv(ch, exitChan)
var total = 0
for _ = range exitChan {
total++
if total == 2 {
break
}
}
fmt.Println("结束....")
}
func send(ch chan<- int, exitChan chan struct{}) {
for i := 0; i < 10; i++ {
ch <- i
}
close(ch)
var a struct{}
exitChan <- a
}
func recv(ch <-chan int, exitChan chan struct{}) {
for {
v, ok := <-ch
if !ok {
break
}
fmt.Println(v)
}
var a struct{}
exitChan <- a
}
2 测试
0
1
2
3
4
5
6
7
8
9
结束....
三 使用 select 解决从管道取数据的阻塞问题
1 代码
package main
import (
"fmt"
"time"
)
func main() {
// 使用 select 可以解决从管道取数据的阻塞问题
// 1 定义一个管道:10个 int 数据
intChan := make(chan int, 10)
for i := 0; i < 10; i++ {
intChan <- i
}
// 2 定义一个管道:5个 string 数据
stringChan := make(chan string, 5)
for i := 0; i < 5; i++ {
stringChan <- "hello" + fmt.Sprintf("%d", i)
}
// 传统的方法在遍历管道时,如果不关闭会阻塞而导致 deadlock
// 问题:在实际开发中,可能我们不好确定什么关闭该管道。
// 解决方案:可以使用 select 方式可以解决。
for {
select {
// 注意: 这里 intChan 一直没有关闭,不会一直阻塞而 deadlock,会自动到下一个case匹配
case v := <-intChan:
fmt.Printf("从intChan读取的数据%d\n", v)
time.Sleep(time.Second)
case v := <-stringChan:
fmt.Printf("从stringChan读取的数据%s\n", v)
time.Sleep(time.Second)
default:
fmt.Printf("都取不到了,不玩了, 程序员可以加入逻辑\n")
time.Sleep(time.Second)
return
}
}
}
2 测试
从intChan读取的数据0
从stringChan读取的数据hello0
从stringChan读取的数据hello1
从intChan读取的数据1
从stringChan读取的数据hello2
从stringChan读取的数据hello3
从intChan读取的数据2
从intChan读取的数据3
从intChan读取的数据4
从intChan读取的数据5
从intChan读取的数据6
从stringChan读取的数据hello4
从intChan读取的数据7
从intChan读取的数据8
从intChan读取的数据9
都取不到了,不玩了, 程序员可以加入逻辑
四 goroutine 中使用 recover,解决协程中出现 panic,导致程序崩溃问题
1 说明
如果我们起了一个协程,但是这个协程出现了 panic,如果我们没有捕获这个 panic,就会造成整个程序崩溃,这时可以在 gorouteing 中使用 recover 来捕获 panic,进行处理,这样即使这个协程发生了问题,主线程仍然不受影响,可以继续执行。
2 代码
package main
import (
"fmt"
"time"
)
// 函数
func sayHello() {
for i := 0; i < 10; i++ {
time.Sleep(time.Second)
fmt.Println("hello,world")
}
}
// 函数
func test() {
// 这里我们可以使用 defer + recover
defer func() {
// 捕获 test 抛出的panic
if err := recover(); err != nil {
fmt.Println("test() 发生错误", err)
}
}()
// 定义了一个 map
var myMap map[int]string
myMap[0] = "golang" // error
}
func main() {
go sayHello()
go test()
for i := 0; i < 10; i++ {
fmt.Println("main() ok=", i)
time.Sleep(time.Second)
}
}
3 测试
main() ok= 0
test() 发生错误 assignment to entry in nil map
hello,world
main() ok= 1
main() ok= 2
hello,world
main() ok= 3
hello,world
hello,world
main() ok= 4
main() ok= 5
hello,world
hello,world
main() ok= 6
main() ok= 7
hello,world
hello,world
main() ok= 8
main() ok= 9
hello,world
hello,world