管道简介
【1】管道(channel)特质介绍:
(1)管道本质就是一个数据结构-队列
(2)数据是先进先出
(3)自身线程安全,多协程访问时,不需要加锁,channel本身就是线程安全的
(4)管道有类型的,一个string的管道只能存放string类型数据
管道入门案例
【1】管道的定义:
var 变量名 chan 数据类型
PS1:chan管道关键字
PS2:数据类型指的是管道的类型,里面放入数据的类型,管道是有类型的,int类型的管道只能写入整数int
PS3:管道是引用类型,必须初始化才能写入数据,即make后才能使用
【2】案例:
func main() {
//定义管道 、 声明管道 ---> 定义一个int类型的管道
var intChan chan int
//通过make初始化:管道可以存放3个int类型的数据
intChan = make(chan int, 3)
//证明管道是引用类型:
fmt.Printf("intChan的值: %v \n",intChan)
//向管道存放数据:
intChan <- -10
num := 20
intChan <- num
intChan <- 40
//注意:不能存放大于容量的数据:
// intChan <- -80
//输出管道的长度:
fmt.Printf("管道的实际长度:%v,管道的容量是:%v \n",len(intChan),cap(intChan))
//在管道中读取数据:
num1 := <-intChan
num2 := <-intChan
num3 := <-intChan
fmt.Println(num1)
fmt.Println(num2)
fmt.Println(num3)
//注意:在没有使用协程的情况下,如果管道的数据已经全部取出,那么再取就会报错:
// num4 := <-intChan
// fmt.Println(num4)
fmt.Printf("管道的实际长度:%v,管道的容量是:%v \n",len(intChan),cap(intChan))
}
管道的关闭
【1】管道的关闭:
使用内置函数close可以关闭管道,当管道关闭后,就不能再向管道写数据了,但是仍然可以从该管道读取数据。
【2】案例:
func main() {
var intChan chan int
intChan = make(chan int, 3)
intChan <- 10
intChan <- 20
//关闭管道:
close(intChan)
//再次写入数据:--->报错
// intChan <- 30
//当管道关闭后,读取数据是可以的:
num := <- intChan
fmt.Println(num)
}
管道的遍历
【1】管道的遍历:
管道支持for-range的方式进行遍历,请注意两个细节
1)在遍历时,如果管道没有关闭,则会出现deadlock的错误
2)在遍历时,如果管道已经关闭,则会正常遍历数据,遍历完后,就会退出遍历。
【2】案例:
func main() {
var intChan chan int
intChan = make(chan int, 100)
for i := 0; i < 100; i++ {
intChan <- i
}
//在遍历前,如果没有关闭管道,就会出现deadlock的错误
//所以我们在遍历前要进行管道的关闭
// for v := range intChan {
// fmt.Println("value = ",v)
// }
close(intChan)
//遍历:for-range
for v := range intChan {
fmt.Println("value = ",v)
}
}
协程和管道协同工作案例
【1】案例需求:
请完成协程和管道协同工作的案例,具体要求:
1) 开启一个writeData协程,向管道中写入50个整数.
2) 开启一个readData协程,从管道中读取writeData写入的数据。
3) 注意: writeData和readDate操作的是同一个管道
4) 主线程需要等待writeData和readDate协程都完成工作才能退出
【2】原理图:
package main
import (
"fmt"
"time"
"sync"
)
var wg sync.WaitGroup
//写:
func writeData(intChan chan int) {
defer wg.Done()
for i := 1; i <= 50; 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, 50)
wg.Add(2)
//开启读和写的协程:
go writeData(intChan)
go readData(intChan)
//主线程一直在阻塞,什么时候wg减为0了,就停止
wg.Wait()
fmt.Println("读写数据完成...")
}
运行结果:
声明只读只写管道
【1】管道可以声明为只读或者只写性质
【2】代码:
package main
import (
"fmt"
)
func main() {
//默认情况下,管道是双向的--》可读可写:
//声明为只写:
// 管道具备<- 只写性质
var intChan chan<- int
intChan = make(chan int, 3)
intChan <- 10
// 报错
// num := <- intChan
fmt.Println("intChan:",intChan)
//声明为只读:
// 管道具备<- 只读性质
var intChan2 <-chan int
if intChan2 != nil {
num1 := <- intChan2
fmt.Println("num1:",num1)
}
// 报错
// intChan2 <- 30
}
管道的阻塞
【1】当管道只写入数据,没有读取,就会出现阻塞:
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
func writeData(intChan chan int) {
defer wg.Done()
for i := 1; i < 10; i++ {
intChan <- i
fmt.Println("写入的数据:",i)
}
close(intChan)
}
func readData(intChan chan int) {
defer wg.Done()
for v := range intChan {
fmt.Println("读取的数据为:",v)
}
}
func main() {
intChan := make(chan int, 10)
wg.Add(2)
go writeData(intChan)
// go readData(intChan)
wg.Wait()
}
运行结果
【2】写的快,读的慢(管道读写频率不一致),不会出现阻塞问题:
package main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
func writeData(intChan chan int) {
defer wg.Done()
for i := 1; i < 10; i++ {
intChan <- i
fmt.Println("写入的数据:",i)
}
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)
wg.Add(2)
go writeData(intChan)
go readData(intChan)
wg.Wait()
}
select功能
【1】select功能:解决多个管道的选择问题,也可以叫做多路复用,可以从多个管道中随机公平地选择一个来执行
PS:case后面必须进行的是io操作,不能是等值,随机去选择一个io操作
PS:default防止select被阻塞住,加入default
【2】代码:
package main
import (
"fmt"
"time"
)
func main() {
intChan := make(chan int, 1)
go func () {
time.Sleep(time.Second * 15)
intChan <- 15
}()
stringChan := make(chan string, 1)
go func () {
time.Sleep(time.Second * 12)
stringChan <- "hellocyz"
}()
//本身取数据就是阻塞的
// fmt.Println(<-intChan)
select {
case v := <-intChan : fmt.Println("intChan:",v)
case v := <-stringChan : fmt.Println("stringChan:",v)
default: fmt.Println("防止select被阻塞")
}
}
defer+recover机制处理错误
【1】问题原因:多个协程工作,其中一个协程出现panic,导致程序崩溃
【2】解决办法:利用defer+recover捕获panic进行处理,即使协程出现问题,主线程仍然不受影响可以继续执行。
【3】案例:
package main
import (
"fmt"
"time"
)
//输出数字:
func printNum() {
for i := 1; i <= 10; i++ {
fmt.Println(i)
}
}
//做除法操作:
func divide() {
defer func () {
err := recover()
if err != nil {
fmt.Println("devide()出现错误:",err)
}
}()
num1 := 10
num2 := 0
result := num1 / num2
fmt.Println(result)
}
func main() {
//启动两个协程:
go printNum()
go divide()
time.Sleep(time.Second * 5)
}
结果: