管道
大约 3 分钟
管道
(先读协程内容先)
管道 (channel)
概括
特性介绍:
本质
本质是一个数据结构:队列 (先进先出)
有类型,例如一个string只能存放string类型数据
自身线程安全,多协程访问时无需加锁,本身线程安全。与协程关系密切
用处
- 可作为流式IO。类似 C++ 的std::cout流那种感觉
- 线程安全,故可和协程配合使用
声明定义、使用
// 语法:如下。引用类型,必须初始化或make后才能写入数据
var 变量名 chan 数据类型
// 示例
package main
import(
"fmt"
)
func main() {
// 声明定义
var intChan chan int
intChan = make(chan int, 3) // 存放3个int类型的数据
fmt.Printf("intChan %v\n", intChane) // 打印地址,表示是引用类型
// 写
intChan <- 10
num := 20
int chan <- num
fmt.Printf("intChan 长%v 容量%v\n", len(intChan), Cap(intChan)) // 2 3
// 读 (先进先出的取)
num1 := <- intChan // 10
num2 := <- intChan // 20
}
方法
关闭管道
关闭管道后,管道只读不可写
// 原型
func close(c chan<- Type)
// 例子
close(intChan)
遍历
管道遍历需要注意:遍历前必须先关闭管道,否则便利到最后时会出现 deadlock 错误
(猜测:关闭管道的原理应该是往管道符的末尾添加一个类似文件结尾 (EOF) 之类的标志,让遍历能自动结束)
package main
import(
"fmt"
)
func main() {
var intChan chan int // 管道
intChan = make(chan int, 100)
// 遍历写
for i:=0; i<100; i++ {
intChan <- i
}
// 关闭管道。遍历前必须先关闭管道,否则便利到最后时会出现 deadlock 错误
close(intChan)
// 遍历读 (for range 版)
for v := range intChan {
fmt.Println(v)
}
}
扩展
(实战) 协程和管道 协同工作
略。正常写就行,注意一下关闭管道和协程计数器的问题就行
有BUG:话说他这案例没有解决读结束时还没写完及关闭管道的问题啊
package main
import(
"fmt"
"time"
"sync"
)
var wg sync.WaitGroup // 协程计数器
// 写
func writeData(intChan chan int) {
defer wg.Done()
for i:=1; i<=20; i++{
intChan <- i
fmt.Println("写入", i)
time.Sleep(time.Second)
}
// 管道关闭
close(intChan)
}
// 读
func readData(intChan chan int) {
defer wg.Done()
for v := range intChan {
fmt.Println("读取", v)
time.Sleep(time.Second)
}
}
func main() {
intChan := make(chan int, 10) // 管道(注意这里管道容量10比循环次数20要小,但由于边写边读没问题,若不读则会发生阻塞)
wg.Add(2)
go writeData(intChan)
go readData(intChan)
wg.wait()
}
只读/只写管道
管道可以声明成只读或只写的(应该是作为形参类型使用的,配合协程函数一个读一个写)
下面这个案例不好,用法没什么意义
package main
import(
"fmt"
)
func main() {
// 可读可写 (默认)
var intChan1 chan int
// 只写
var intChan2 chan<- int
intChna2 = make(chan int, 3)
// 只读
var intChan3 <-chan int
}
管道的阻塞
(这章有BUG,教程太拉了。命名是Add(2)导致死锁的原因,不关阻塞的事)
当管道只写不读,管道 (缓冲区) 可以会满,导致阻塞。 但是 写快读慢 则不会出现阻塞问题。(???,会吧,垃圾教程)
select case 语法
select功能:
- 解决多个管道的选择问题,也叫多路复用。可以从多个管道中随机公平地选择一个执行
- 也可以解决阻塞问题
注意项:
- case后面必须是io操作,不能是等值,随机去选择一个io操作
- default可防止select被阻塞住,加入default
package main
import (
"fmt"
)
func main() {
// int管道
intChan := make(chan int, 1)
go func() {
time.Sleep(time.Second*3)
intChan <- 10
}()
// string管道
stringChan := make(chan string, 1)
go func(){
time.Sleep(time.Second*2)
stringChan <- "msbgolang"
}
// fmt.Println(<-intChan) // 取数据本身就阻塞
select{
case v := <-intChan:
fmt.Println("intChan:", v)
case v := <-stringChan:
fmt.Println("intChan:", v)
default:
fmt.Println("防止阻塞")
}
}
defer + recover 机制处理错误
背景:多个协程工作,避免 其中一个协程出现panic 导致程序崩溃
案例:就正常写。协程函数里加个 defer func(){ err:= recover; if err != nil {...}}
就行了