前言
这是我参与「第五届青训营 」伴学笔记创作活动的第 11 天,其实第五届青训营已经正式开课n多天了,笔记不断更新中,都是我听完课之后的总结和平时自己的学习积累,分享出来给有需要的朋友。
本文内容
本文将涉及到Go语言管道channel的基本使用。
Go语言基础
1.GO语言管道channel
(1)管道的介绍
(1)channel本质就是一个数据结构-队列
(2)数据是先进先出【FIFO】
(3)线程安全,多goroutine访问时,不需要加锁,就是说channel本身就是线程安全的
(4)channel是有类型的,一个string的channel只能存放string类型数据
(2)管道的基本使用
定义/声明channel
var/声明channel
var 变量名 chan 数据类型
示例:
var intChan chan int(intChan用于存放int数据)
var mapChan chan map[int]string (mapChan用于存放map[int]string类型)
var perChan chan Person
var perChan2 chan *Person
说明:
1)channel是引用类型
2)chanel必须初始化才能写入数据,即make后才能使用
3)管道是有类型的,intChan只能写入整数int
package main
import (
"fmt"
)
func main() {
//演示一下管道的使用
//1.创建一个存放3个int类型的管道
var intChan chan int
intChan = make(chan int, 3)
//2.看看intChan是什么
fmt.Printf("intChan 的值=%v intChan本身的地址=%p\n", intChan, &intChan)
//3.向管道写入数据
intChan <- 10
num := 211
intChan <- num
intChan <- 98
//4.看看管道的长度和cap容量
fmt.Printf("intChan 的长度=%v intChan容量=%v\n", len(intChan), cap(intChan))
//注意点,当我们给管道写入数据时,个数不能超过其容量
// intChan <- 50 //已经满了,这个再写入就报错
// fmt.Printf("intChan 的长度=%v intChan容量=%v\n", len(intChan), cap(intChan))
//5. 从管道中读取数据
var num2 int
num2 = <-intChan
fmt.Println("num2=", num2)
fmt.Println("channel len=%v cap=%v \n", len(intChan), cap(intChan)) //2,3
num3 := <-intChan
num4 := <-intChan
fmt.Println("num3=", num3, "num4=", num4)
//6.在没有使用协程的情况下,如果我们的管道数据已经全部取出,再取就会报告 deadlock
// num5 := <-intCchan //已经全部取出了,再取出就报错
// fmt.Println("num5=", num5)
}
(3)GO语言管道的应用
package main
import (
"fmt"
)
type Cat struct {
Name string
Age int
}
func main() {
//2.定义一个存放任意数据类型的管道 3个数据
//var allChan chan interface{}
allChan := make(chan interface{}, 3)
allChan <- 10
allChan <- "tom jack"
cat := Cat{"小花猫", 4}
allChan <- cat
//我们希望获得管道中第三个元素,则先将前两个推出
<-allChan
<-allChan
newCat := <-allChan //从管道中取出的Cat是什么?
fmt.Printf("newCat=%T,newCat=%v\n", newCat, newCat)
//下面的写法是错误的!编译不通过
//fmt.Printf("newCat.Name=%v\n",newCat.Name)
//使用类型断言
a := newCat.(Cat)
fmt.Printf("newCat.Name=%v\n", a.Name)
//3.创建一个mapChan,最多可以存放10个map[string]string的key-val,演示写入和读取
var mapChan chan map[string]string
mapChan = make(chan map[string]string, 10)
m1 := make(map[string]string, 20)
m1["city1"] = "北京"
m1["city2"] = "上海"
m2 := make(map[string]string, 20)
m2["hero1"] = "宋江"
m2["hero2"] = "武松"
//存入mapChan
mapChan <- m1
mapChan <- m2
//先进先出 所以从mapChan第一个取出的是m1
city := <-mapChan
fmt.Printf("m1为%v\n", city)
fmt.Printf("m1城市1为%v\n", city["city1"])
}
(4)GO语言管道的遍历
channel支持for-range的方式进行遍历,请注意两个细节
1) 在遍历时,如果channel没有关闭,则会出现deadlock的错误
2)在遍历时。如何channel已经关闭,则会正常遍历数据,遍历完后,就会退出遍历
package main
import (
"fmt"
)
func writeData(intChan2 chan int) {
//遍历管道
for i := 0; i < 100; i++ {
intChan2 <- i * 2 //放入100个数据到管道
fmt.Println("writeData", i)
}
close(intChan2) //关闭
}
func readData(intChan2 chan int, exitChan chan bool) {
//遍历管道
for {
v, ok := <-intChan2
if !ok {
break
}
fmt.Println("v=", v)
}
exitChan <- true //还在读就写入一个值,让主循环不退出
close(exitChan) //关闭管道
//在遍历时,如果channel没有关闭,则会出现deadlock的错误
//在遍历时,如何channel已经关闭,则会正常遍历数据。遍历完后,就会推出遍历
}
func main() {
intChan := make(chan int, 3)
intChan <- 100
intChan <- 200
close(intChan) //关闭管道close
//这是不能够再写入数到channel
//intChan <- 300
fmt.Println("okok")
//当管道关闭后,读取数据是可以的
n1 := <-intChan
fmt.Println("n1=", n1)
//创建两个管道
intChan2 := make(chan int, 100)
exitChan := make(chan bool, 1)
//遍历管道不能使用普通的for循环,只能取到一半值
// for i :=0; i<len(intChan2);i++{
// }
go writeData(intChan2)
go readData(intChan2, exitChan)
for {
_, ok := <-exitChan
if !ok { //这里是根据是否能正常取到数据判断的,因为如果每次读都会往exitChan内写入一个值
break
}
}
}
(5)GO语言管道的注意事项
1.管道可以声明只读或者只写 默认是双向的
(1)var myChan chan int //声明可读可写
(2)var myChan chan<- int //声明只写
(3)var myChan <-chan int //声明只读
2.使用select可以解决从管道取数据阻塞问题(下面例子)
func main() {
fmt.Println()
//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.Println("都取不到了,不玩了")
time.Sleep(time.Second)
return
}
}
}
总结
- channel 可以声明为只读,或者只写性质
- 使用 select 可以解决从管道取数据的阻塞问题
- 管道是类型相关的,即一个管道只能传递一种类型的值
写在最后
本文是我的日常学习笔记,如果哪里有写错,麻烦请指出来,感谢。这里我也推荐大家多写笔记,写笔记是一个很好的习惯,可以帮助我们更好的吸收和理解学习的新知识,新的一年大家一起加油!