3.8 Go之通道(chan)

3.8 Go之通道(chan)

什么是通道?

goroutine的通信管道

如何李姐通道

  • 首先不要混淆goroutinechannel之间的关系

  • goroutine是并发的结构体

  • channel是每个结构体通信的桥梁

channel的特点:

channel是一个通信机制,每个channel都有一个特殊的类型,一个可以发送int类型的channel一般写为chan int go的并发特点:

使用通道代替共享内存

goroutinechannel关系:

 

多个goroutine争抢数据会造成执行的低效率,使用队列的方式是最高效的。channel是队列一样的结构

Go通道种类

  • 单向通道

  • 无缓冲通道

  • 带缓冲通道


通道的特性

特点:

  • 任何时候同时只能有一个goroutine访问通道进行发送和获取数据

  • 遵循先入先出的规则(First In First Out)

声明通道类型(通道是一种类型)

通道的声明方式:

var 通道变量 chan 通道类型;
  • 通道类型:通道内的数据类型。

  • 通道变量:保存通道的变量。

chan类型的空值是nil,声明后需要make才能使用

创建通道

通道的特点:

通道是引用类型,要使用make进行创建。

声明通道实例的方式:

通道实例 := make(chan 数据类型)
  • 数据类型:通道内传输的元素类型。

  • 通道实例:通过make创建的通道句柄。

实例:

ch1 := make(chan int) // 存放int类型数据的通道
ch2 := make(chan interface{}) // 创建一个空接口类型的通道, 可以存放任意格式

type Equip struct{/* 一些字段 */}
ch2 := make(chan *Equip) // 创建Equip指针类型的通道, 可以存放*Equip

使用通道发送数据

  • 通道发送数据的格式

  • 通过通道发送数据的例子

  • 发送将持续阻塞直到数据被接收


通道发送数据的格式

使用特殊操作符<-,格式为:

通道变量 <- 

箭头指向make创建好的通道实例,将值传入通道实例当中

  • 通道变量:通过make创建好的通道实例。

  • 值:可以是变量、常量、表达式或者函数返回值等。值的类型必须与ch通道的元素类型一致。

通道发送数据的例子

make创建通道实例,<-向通道发送数据:

// 创建一个空接口通道
ch := make(chan interface{})
// 将int类型数据放入通道中
ch <- 0
// 将string类型的数据放入通道中
ch <- "Hello,World!"
通道内的数据如果没有被接收将持续阻塞

把数据往通道中发送时,如果接收方一直都没有接收,那么发送操作将持续阻塞。

package main

func main() {
   // 创建一个通道
   ch := make(chan int)

   // 将一个值放入通道
   ch <- 0
}
/*
结果将持续阻塞,执行后会报错:
fatal error: all goroutines are asleep - deadlock!
*/

使用通道接收数据

  • 阻塞接收数据

  • 非阻塞接收数据

  • 接收任意数据,忽略接收的数据

  • 循环接收


通道接收数据的特点:

  1. 使用操作符<-

  2. 通道的手法操作在两个goroutine中进行,若通道没有接收方则会持续阻塞。

  3. 接收持续阻塞直到发送方发送数据。

  4. 每次接收一个元素。

阻塞接收数据

<始终指向接收方

data := <-ch
/*
执行该语句时将会阻塞,直到接收到数据并赋值给 data 变量。
*/
非阻塞接收数据

格式:

data, ok := <-ch
  • data:表示接收到的数据。未接收到数据时,data为通道类型的零值。

  • ok:表示是否接收到数据。

接收任意数据,忽略接收的数据

阻塞接收数据,忽略从通道返回的数据:

<-ch
/*
特点:
1、执行语句会发生阻塞,直到接收到数据
2、接收到的数据会被忽略
*/

作用:

通过通道在goroutine间阻塞收发实现并发同步

示例:

package main

import "fmt"

func main() {
   // 构建一个通道--->int类型的
   ch := make(chan int)

   // 开启一个匿名函数--->实现往通道中写入数据的功能
   go func() {
       // 开启goroutine
       fmt.Println("开启goroutine:")

       // 向通道当中写入数据
       ch <- 0

       // 关闭goroutine
       fmt.Println("关闭goroutine!")
  }()

   // 开始匿名等待
   fmt.Println("等待goroutine!")

   // 匿名等待
   <-ch
   /*
   开启 goroutine 后,马上通过通道等待匿名 goroutine 结束。
   */

   // 结束
   fmt.Println("结束!")
}

分析:

<-ch会忽略接收的内容。所以不会报死锁的错误。

循环接收

借用for range语句进行多个元素接收:

for data := range ch {
   
}
/*
通道可遍历
*/

示例代码:

package main

import (
   "fmt"
   "time"
)

/*
遍历通道内容:
1、创建一个通道
2、循环往通道当中添加数据
3、使用for range循环读取通道当中的数据以及类型
*/
func main() {
   // 构建一个通道
   ch2 := make(chan int)

   // 创建一个匿名goroutine循环向通道当中添加数据
   go func() {
       // 循环添加0->3
       for i := 3; i >= 0 ; i-- {
           // 向通道当中发送数据
           ch2 <- i

           // 发送一次等待一秒
           time.Sleep(time.Second)
      }
  }()

   // 通过循环遍历读取通道内的数据--->遍历不是接收,不会造成死锁
   for data := range ch2 {
       fmt.Println(data)

       // 读到0跳出循环
       if 0 == data {
           break
      }
  }
}
/*
当接收到数值 0 时,停止接收。如果继续发送,由于接收 goroutine 已经退出,没有 goroutine 发送到通道,因此运行时将会触发宕机报错。
*/s
posted @ 2022-03-21 09:23  俊king  阅读(168)  评论(0)    收藏  举报