(1).前面使用全局变量加锁同步来解决 goroutine 的通讯,但不完美
(2).主线程在等待所有goroutine 全部完成的时间很难确定,这里设置 10 秒,仅仅是估算
(3).如果主线程休眠时间长了,会加长等待时间,如果等待时间短了,可能还有goroutine 处于工作状态,这时也会随主线程的退出而销毁
(4).通过全局变量加锁同步来实现通讯,也并不利于多个协程对全局变量的读写操作
(5).从上面的分析来看,使用一种新的通讯机制是非常必要的,这个新的通讯机制就是管道-channel
(1).channel本质就是一个数据结构一队列【 示意图如下】
(2).数据是先进先出 【 FIFO:first in first out】
(3).线程安全,多goroutine 访间时,不需要加锁,就是说channel本身就是线程安全的
(4).channel是有类型的,一个 string 的channel只能存放 string 类型数据
示意图如下:
var 变量名 chan 数据类型
eg:
var intChan chan int //intChan用于存放int数据
var mapChan chan map[int]string // mapChan用于存放map[int]string类型
var perChan chan Person //perChan用于存放Person结构体
var perChan2 chan *Person //perChan2用于存放Person结构体指针
...
说明:
channel是引用类型
channel必须初始化才能写入数据,即make后才能使用
channel是有类型的,intChan只能写入整数int
- package main
-
- import (
- "fmt"
- )
-
- func main() {
- //演示管道的使用
- //1.创建一个可以存放3个int的管道
- var intChan chan int
- intChan = make(chan int, 3)
- //2.看看intChan是什么
- //intChan 的值:0xc0000d8080, inChan本身的地址:0xc0000d2018
- fmt.Printf("intChan 的值:%v, inChan本身的地址:%p\n", intChan, &intChan)
- //3.向管道插入数据
- intChan<- 1
- num := 2
- intChan<- num
- //注意:当向管道写入数据时,不能超过其容量
- //4.查看管道的容量以及长度
- //intChan 的长度:2, inChan的容量:3
- fmt.Printf("intChan 的长度:%v, inChan的容量:%v\n", len(intChan), cap(intChan))
- //5.从管道读取数据
- var num2 int
- num2 = <-intChan
- fmt.Printf("num2=%v\n", num2)//num2=1
- //intChan 的长度:1, inChan的容量:3
- fmt.Printf("intChan 的长度:%v, inChan的容量:%v\n", len(intChan), cap(intChan))
- //6.在没有使用协程的情况下,如果管道的数据已经被全部取出,再取就会报错(deallock)
- }
(1).channel中只能存放指定的数据类型
(2).channel的数据放满后,就不能再放入了
(3).如果从channel取出数据后,可以继续放入
(4).在没有使用协程的情况下,如果channel数据取完了,再取,就会报 dead lock
6.案例
(1).创建一个intChan,最多可以存放3个int,演示存3个数据到intChan,然后再取出这三个int
(2).创建一个mapChan,最多可以存放10个map[string][string]的key-value,演示写入和读取
(3).创建一个catChan,最多可以存放10个Cat结构体变量,演示写入和读取
(4).创建一个catChan2,最多可以存放10个*Cat结构体变量,演示写入和读取
(5).创建一个allChan,最多可以存放10个任意数据类型变量,演示写入和读取的用法
(6).案例:
1).创建一个 Person 结构体 (Name , Age , Address )
2).使用 rand 方法配合随机创建10个Person 实例,并放入到 channel 中
3).遍历channel ,将各个 Person 实例的信息显示在终端
- package main
-
- import (
- "fmt"
- "math/rand"
- "time"
- "strconv"
- )
-
- type Person struct {
- Name string
- Age int
- Address string
- }
-
- func main() {
- //初始化随机数
- rand.Seed(time.Now().UnixNano())
- //创建一个存放10个Person实例的管道
- personChan := make(chan Person, 10)
- temp := Person{}
- for i := 1; i <= 10; i++ {
- temp = Person {
- Name : "Person" + strconv.Itoa(rand.Intn(100)),
- Age : rand.Intn(100),
- Address : "成都市华府大道" + strconv.Itoa(rand.Intn(100)) + "号",
- }
- //把person放入管道中
- personChan <- temp
- }
- //循环从管道中读取数据
- lenPersonChan := len(personChan)
- // temp1 := Person{}
- // var personTemp interface{}
- for i := 1; i <= lenPersonChan; i++{
- personTemp := <-personChan
- fmt.Printf("第%v个person数据的Name=%v,Age=%v, Address=%v\n", i,
- personTemp.Name, personTemp.Age, personTemp.Address)
- }
- }
使用内置函数 close可以关闭channel,当 channel 关闭后,就不能再向channel写数据了,但是仍然可以从channel读取数据
- package main
-
- import(
- "fmt"
- )
-
- func main() {
- //定义并初始化一个管道
- intChan := make(chan int, 3)
- intChan <- 100
- intChan <- 300
- close(intChan) // close 管道,当close后,不能写入数据到intChan
- // intChan <- 200 // 写入失败:send on closed channel,因为管道已关闭
- fmt.Println("ok")
- //当管道关闭后,还是可以读取数据的
- n1 := <- intChan
- fmt.Printf("n1:=%v\n", n1)
- }
channel支持 for-range 的方式进行遍历,请注意两个细节
1).在遍历时,如果channel没有关闭,则回出现 deadlock的错误
2).在遍历时,如果 channel已经关闭,则会正常遍历数据,遍历完后,就会退出遍历
- package main
-
- import(
- "fmt"
- )
-
- func main() {
- //定义并初始化一个管道
- intChan := make(chan int, 3)
- intChan <- 100
- intChan <- 300
- close(intChan) // close 管道,当close后,不能写入数据到intChan
- // intChan <- 200 // 写入失败:send on closed channel,因为管道已关闭
- fmt.Println("ok")
- //当管道关闭后,还是可以读取数据的
- n1 := <- intChan
- fmt.Printf("n1:=%v\n", n1)
-
- //遍历管道
- intChan1 := make(chan int, 100)
- for i := 0; i < 100; i++ {
- intChan1 <- i * 2 //放入100个数到管道
- }
-
- //遍历管道时,不能用普通的for循环
- //在遍历时,如果没有关闭chan,则会出现deallock错误
- ///在遍历时,如果以及已经关闭chan,则会正常遍历数据,遍历完后,退出
- close(intChan1)
- for v := range intChan1 {
- fmt.Println("v=", v)
- }
- }