GO语言基础入门-管道的使用 | 青训营笔记

196 阅读5分钟

前言

这是我参与「第五届青训营 」伴学笔记创作活动的第 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 可以解决从管道取数据的阻塞问题
  • 管道是类型相关的,即一个管道只能传递一种类型的值

写在最后

本文是我的日常学习笔记,如果哪里有写错,麻烦请指出来,感谢。这里我也推荐大家多写笔记,写笔记是一个很好的习惯,可以帮助我们更好的吸收和理解学习的新知识,新的一年大家一起加油!