Go语言通道讲解

一、浅谈通道

       如果说 goroutine 是 Go语言程序的并发体的话,那么 channels 就是它们之间的通信机制。一个 channels 是一个通信机制,它可以让一个 goroutine 通过它给另一个 goroutine 发送值信息。每个 channel 都有一个特殊的类型,也就是 channels 可发送数据的类型。一个可以发送 int 类型数据的 channel 一般写为 chan int。

      Go语言提倡使用通信的方法代替共享内存,当一个资源需要在 goroutine 之间共享时,通道在 goroutine 之间架起了一个管道,并提供了确保同步交换数据的机制。声明通道时,需要指定将要被共享的数据的类型。可以通过通道共享内置类型、命名类型、结构类型和引用类型的值或者指针。

这里通信的方法就是使用通道(channel),如下图

                   

 

                                                     图:goroutine 与 channel 的通信

        在地铁站、食堂、洗手间等公共场所人很多的情况下,大家养成了排队的习惯,目的也是避免拥挤、插队导致的低效的资源使用和交换过程。代码与数据也是如此,多个 goroutine 为了争抢数据,势必造成执行的低效率,使用队列的方式是最高效的,channel 就是一种队列一样的结构。

二、通道的特性  

  通道类型是Go自带的,相当于是一个先进先出的队列,同时唯一一个可以满足并发安全性的类型。声明一个通道类型的变量的时候,首先需要确定通道元素类型,然后要确定通道的容量,当然默认容量是0;

三、声明通道类型

通道本身需要一个类型进行修饰,就像切片类型需要标识元素类型。通道的元素类型就是在其内部传输的数据类型,声明如下:

var 通道变量 chan 通道类型

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

创建通道

通道是引用类型,需要make进行创建,格式如下:

通道实例 := make(chan 数据类型)

初始化

使用make进行初始化,如下

  • 值:可以是变量、常量、表达式或者函数返回值等。值的类型必须与ch通道的元素类型一致。
  • 通道变量:通过make创建好的通道实例
  • ch1 := make(chan int)                 // 创建一个整型类型的通道
  • ch2 := make(chan interface{})         // 创建一个空接口类型的通道, 可以存放任意格式
  • type Equip struct{ /* 一些字段 */ }
  • ch2 := make(chan *Equip)             // 创建Equip指针类型的通道, 可以存放*Equip
  • 数据类型:通道内传输的元素类型。
  • 通道实例:通过make创建的通道句柄。
  • 通道类型:通道内的数据类型。
  • 通道变量:保存通道的变量。

如果不指定容器,默认通道的容器0,这种通道也成为非缓冲通道。

四、使用通道发送数据

通道创建后,就可以使用通道进行发送和接收操作。

1)通道发送数据的格式

通道的发送使用特殊的操作符<-,将数据通过通道发送的格式为:

      通道变量 <- 值

2)通过通道发送数据的例子

使用make创建一个通道后,就可以使用<-向通道发送数据,代码如下:

// 创建一个空接口通道

ch := make(chan interface{})

// 将0放入通道中

ch <- 0

// 将hello字符串放入通道中

ch <- "hello"

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

把数据往通道中发送时,如果接收方一直没有接收,那么发送操作将持续阻塞。Go程序运行时能智能的发现一些永远无法发送成功的语句并作出提示,代码如下:

package main

func main() {

        //创建一个整形通道

        ch := make(chan int)

        //尝试将0通道发送

        ch <- 0

}

运行结果:

fatal error: all goroutines are asleep - deadlock!

 

goroutine 1 [chan send]:

main.main()

E:/goProject/src/test/chan.go:7 +0x57

exit status 2

报错的意思是:运行时发现所有的goroutinue(包括main)都处于等待gorutine。也就是说goroutine的channel并没有形成和发送接收对应的代码。

 

五、使用通道接收数据

通道接收同样使用<-操作符,通道接收如下特性:

1、通道的收发操作在不同的两个goroutine间进行;

由于通道的数据在没有接收让处理时,数据发送方会持续阻塞,因此通道的接收必定在另外一个goroutine中进行。

2、接收将持续阻塞直到发送方发送数据;

如果接收方案接收时,通道中没有发送方发送数据,接收方也会发生阻塞,直到发送方发送数据为止;

3、每次接收一个元素

通道一次只能接收只能接收一个数据元素

通道的数据接收一共以下4种写法:

1)阻塞接收数据

阻塞模式接收数据时,将接收变量作为<-操作符的左值,格式如下:

 data:=<-ch

执行该语句时将会阻塞,直到接收到数据并赋值给 data 变量。

2)非阻塞接收数据

使用非阻塞方式从通道接收数据时,语句不会发生阻塞,格式如下:

 data,ok :=<-ch

  • data:表示接收到的数据。未接收到数据时,data为通道类型的零值;
  • Ok: 表示是否接收到数据

非阻塞的通道接收方法可能造成高的CPU占用,因此使用非常少。如果需要实现接收超时检测,可以配合select和计时器channel进行,可以参见后续的内容;

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

阻塞接收数据后,忽略从通道返回的数据,格式如下:

<-ch

执行该语句时将会发生阻塞,直到接收到数据,但接收到的数据会被忽略。这个方式实际上只是通过通道在 goroutine 间阻塞收发实现并发同步。

使用通道做并发同步的写法,可以参考下面的例子:

package main

 

import (

        "fmt"

)

 

func main() {

        // 构建一个通道

        ch := make(chan int)

        // 开启一个并发匿名函数

        go func() {

                fmt.Println("start goroutine")

                // 通过通道通知maingoroutine

                ch <- 0

                fmt.Println("exit goroutine")

        }()

        fmt.Println("wait goroutine")

        // 等待匿名goroutine

        <-ch

        fmt.Println("all done")

}

 

结果输出:

wait goroutine

start goroutine

exit goroutine

all done

4)循环接收

通道的数据接收可以借用 for range 语句进行多个元素的接收操作,格式如下:

for data := range ch {

}

通道 ch 是可以进行遍历的,遍历的结果就是接收到的数据。数据类型就是通道的数据类型。通过 for 遍历获得的变量只有一个,即上面例子中的 data。

遍历通道数据的例子请参考下面的代码。

使用 for 从通道中接收数据:

package main

 

import (

        "fmt"

        "time"

)

 

func main() {

        // 构建一个通道

        ch := make(chan int)

        // 开启一个并发匿名函数

        go func() {

                //3循环到0

                for i := 3; i >= 0; i-- {

                        //发送30之间的数值

                        ch <- i

                        //每次发送完成时等待

                        time.Sleep(time.Second)

                }

        }()

        //遍历接收通道数据

        for data := range ch {

                //打印通道数据

                fmt.Println(data)

                //当遇到数据0时,退出接收循环

                if data == 0 {

                        break

                }

        }

}

执行代码,输出如下:

3

2

1

0

代码说明如下:

  • 第 12 行,通过 make 生成一个整型元素的通道。
  • 第 15 行,将匿名函数并发执行。
  • 第 18 行,用循环生成 3 到 0 之间的数值。
  • 第 21 行,将 3 到 0 之间的数值依次发送到通道 ch 中。
  • 第 24 行,每次发送后暂停 1 秒。
  • 第 30 行,使用 for 从通道中接收数据。
  • 第 33 行,将接收到的数据打印出来。
  • 第 36 行,当接收到数值 0 时,停止接收。如果继续发送,由于接收 goroutine 已经退出,没有 goroutine 发送到通道,因此运行时将会触发宕机报错
发布了37 篇原创文章 · 获赞 19 · 访问量 857

猜你喜欢

转载自blog.csdn.net/zuiyijiangnan/article/details/104202816