在遍历时,若channel没有关闭,出现deadlock错误;在遍历时,若channel已经关闭,会正常遍历数据,遍历完之后,就会退出遍历;
举个栗子:
package main
import "fmt"
func main () {
// 创建一个管道, 大小为200
intChan := make(chan int, 200)
for i := 0; i < 200; i++ {
intChan<- i * 2
}
// 在遍历取值时, 一定要关闭管道
close(intChan)
// 遍历, 取出管道所有的值
for value := range intChan{
fmt.Println("value:", value)
}
}
如果在遍历取值的时候,不关闭管道会报错:fatal error: all goroutines are asleep - deadlock!
2.5.协程与管道的使用:
package main
import "fmt"
// 往管道里写入50条数据
func writeDate(intChan chan int) {
for i := 1; i <= 50; i++{
// 写入数据
intChan<- i
fmt.Println("管道中写入数据:", i)
}
// 写完后,关闭此管道
close(intChan)
}
// 从管道中读取数据
func readData(intChan chan int, exitChan chan bool) {
for {
v, ok := <-intChan
// 说明intChan管道已经取完了
if !ok{
break
}
fmt.Printf("intChan 管道取出数据:%v\n", v)
}
// readData 取完后表示任务已经完成
exitChan<- true
close(exitChan)
}
func main() {
// 创建两个管道
intChan := make(chan int, 50)
// 退出管道, 主线程监控, 协程取完intChan后, 会写进此管道一条数据
exitChan := make(chan bool, 1)
// 开启写的协程、读的协程
go writeDate(intChan)
go readData(intChan, exitChan)
// 写一个for循环, 监听exitChan管道, 若exitChan管道的数据取完, 主线程可以结束
for {
_, ok := <- exitChan
if !ok{
break
}
}
}
切记,这里创建两个管道,是解决,主线程退出,协程还没有执行完,该程序就结束的问题;如果指向管道写入数据,而没有读取,就会出现阻塞dead lock,原因是超出了管道的容量;
2.6.管道使用细节:
声明管道为只写: var chan2 chan<- int
chan2 = make(chan int, 3)
chan2<-20
声明管道为只读: var chan3 <-chan int
num := <- chan3
只读或只写,可以应用到函数传参时,做严格校验; select可以解决从管道取数据阻塞问题: package main
import (
"fmt"
"time"
)
func main() {
// 使用select 可以解决管道数据堵塞的问题
// 1.定义一个int类型管道, 大小为10
intChan := make(chan int, 10)
for i := 0; i < 10; i++{
intChan<- i
}
// 2.定义一个管道 5个数据string
strChan := make(chan string, 5)
for i := 0; i < 5; i++ {
strChan<- "hello" + fmt.Sprintf("%s", i)
}
// 传统方法在遍历管到时, 如果不关闭会阻塞 导致deadlock
// 实际开发中,有时不确定什么时候关闭该管道
for {
select {
// 注意:若intChan 一直没有关闭, 不会一直阻塞而导致deadlock
// 会自动到下一个case匹配
case v := <- intChan:
fmt.Printf("从intChan读取数据%d\n", v)
time.Sleep(time.Second)
case v := <- strChan:
fmt.Printf("从strChan读取数据%s\n", v)
time.Sleep(time.Second)
default:
fmt.Printf("取不到数据咯~~~\n")
return
}
}
}
协程中使用recover,解决协程中出现panic,导致程序崩溃问题: package main
import (
"fmt"
"time"
)
func sayGo() {
// 写一个正常运行的函数
for i := 0; i < 10; i++{
time.Sleep(time.Second)
fmt.Println("hello golang")
}
}
func testErr() {
// 使用defer + recover 进行对此函数的异常捕获
defer func() {
if err := recover(); err != nil{
fmt.Printf("test()函数发生错误:%v", err)
}
}()
// 写一个错误的函数
var myMap map[int]string
myMap[0] = "hello"
}
func main() {
go sayGo()
go testErr() // 若此函数错误, 不会影响到其余函数 所以在此函数里加上捕获异常
for i := 0; i < 10; i++{
fmt.Println("main() ok=", i)
time.Sleep(time.Second)
}
}
本文来自:51CTO博客
感谢作者:wx592a7561e9493
查看原文:Go 语言 协程和管道讲解
相关阅读 >>
更多相关阅读请进入《Go》频道 >>

Go语言101
一个与时俱进的Go编程知识库。