Go语言学习之使用channel|Go主题月

293 阅读2分钟

作者:看那个码农

公众号:看那个码农

上期内容介绍了Go语言学习之键盘输入与随机数|Go主题月

  • 键盘输入
  • 随机数

本篇内容将继续带领大家走进Go语言的世界。

1.本文简介

Go语言学习之使用channel

2.channel的来源

Go语言中,单纯将函数并发执行是没有意义的。函数与函数之间需要交换数据才能体现并发执行函数的意义。 虽然在Go语言程序中可以使用共享内存进行数据交换,但是共享内存在不同的goroutine中容易发生竞态问题

因此,Go语言提倡使用通信的方式代替共享内存,这里通信的方法就是使用通道channel

使用通道channel实现多个goroutine间的通信。

3.channel的特性

Go语言中的通道channel是一种特殊的类型。

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

通道像一个传送带或者队列,遵循先入先出的规则。

4.创建channel类型

4.1 声明channel类型

声明channel类型的语法格式如下:

var channel 变量 chan channel类型

  • channel变量:保存通道内的变量。
  • channel类型:通道内的数据类型。

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

4.2 创建channel类型

具体channel类型的创建格式如下:

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

用法如下:

//创建一个整数类型channel
ch1 := make(chan int)

//创建一个空接口类型channel
ch2 := make(chan interface{})

5.使用channel发送数据

通过channel发送数据需要使用特殊的操作符“<-

用法如下:

channel 变量 <-值

channel发送值的类型必须与channel的元素类型一致。

如果接收方一直没有接收,那么发送操作将持续阻塞。

6.使用channel接收数据

channel收发操作是在不同的两个goroutine间进行。

方式有以下4种:

6.1 阻塞接收数据

channel接收使用特殊的操作符“<-

用法如下:

data := <-ch

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

代码案例如下:

package main

import "fmt"

func sendData(ch1 chan string)  {
   defer close(ch1)
   for i:=0;i<3;i++{
      ch1 <- fmt.Sprintf("发送数据%v\n",i)
   }

}

func main() {

   ch1 :=make(chan string)
   go sendData(ch1)
   for{
      data := <-ch1
      if data == ""{
         break
      }
      fmt.Println("从通道中读取数据方式1:",data)
   }
}

输出为:

image.png

6.2 完整接收数据

阻塞接收数据的完整写法如下所示:

data,ok := <-ch

  • data表示接收到的数据。未接收到数据时,datachannel类型的零值。
  • ok布尔类型)表示是否接收到数据。

用法如下:

package main

import "fmt"

func sendData(ch1 chan string)  {
   defer close(ch1)
   for i:=0;i<3;i++{
      ch1 <- fmt.Sprintf("发送数据%v\n",i)
   }

}

func main() {

   ch1 :=make(chan string)
   go sendData(ch1)
   for{
      data,ok := <-ch1
      fmt.Println(ok)
      if !ok{
         break
      }
      fmt.Println("从通道中读取数据方式2:",data)
   }
}

输出为:

image.png

6.3 忽略接收数据

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

用法如下:

<-ch

执行该语句时channel将会阻塞。该语句目的不在于接收channel中的数据,而是为了阻塞goroutine

6.4 循环接收数据

循环接收数据,需要配合使用关闭channel,借助普通for循环for…range语句循环接收多个元素。遍历channel,遍历到的结果就是接收到的数据,数据类型就是channel的数据类型。

  • 普通for循环接收channel数据,需要有break循环的条件;
  • for…range会自动判断出channel已关闭,而无须通过判断来终止循环。

用法如下:

package main

import "fmt"

func sendData(ch1 chan string)  {
   defer close(ch1)
   for i:=0;i<3;i++{
      ch1 <- fmt.Sprintf("发送数据%v\n",i)
   }

}

func main() {

   ch1 :=make(chan string)
   go sendData(ch1)
   for value:=range ch1{
      fmt.Println("从通道中读取数据方式3:",value)
   }
}

输出为:

image.png

7.阻塞

channel默认是阻塞的。当数据被发送到channel时会发生阻塞,直到有其它goroutine从该channel中读取数据。当从channel读取数据时,读取也会被阻塞,直到其它goroutine将数据写入该channel

用法如下:

package main

import "fmt"

func main() {

   ch1 :=make(chan int)
   fmt.Printf("%T\n",ch1)

   ch2 :=make(chan bool)

   go func() {
      data,ok := <- ch1
      if ok{
         fmt.Println("子goroutine取到的数值为:",data)
      }
      ch2 <-true
   }()

   ch1<-10
   <-ch2

   fmt.Println("main over...")
}

输出为:

image.png

8.关闭channel

发送方如果数据写入完毕,需要关闭channel用于通知接收方数据传递完毕

通常情况是发送方主动关闭channel。接收方通过多重返回值判断channel是否关闭,如果返回值是false,则表示channel已经关闭

用法如下:

package main

import "fmt"

func main() {

   ch := make(chan int)

   close(ch)

   fmt.Printf("通道的指针:%v\n通道的容量:%v\n通道的长度:%v\n",ch,cap(ch),len(ch))
}

输出为:

image.png

往关闭的channel中写入数据会报错。

代码如下:

package main

import "fmt"

func main() {

   ch := make(chan int)

   close(ch)

   fmt.Printf("通道的指针:%v\n通道的容量:%v\n通道的长度:%v\n",ch,cap(ch),len(ch))

   ch <-1
}

输出为:

image.png

9.缓冲channel

默认创建的channel都是非缓冲channel,读写都是即时阻塞

带缓冲通道在发送时无需等待接收方接收即可完成发送过程,并且不会发生阻塞

因此,可以在非缓冲通道的基础上,为通道增加一个有限大小的存储空间形成带缓冲通道

9.1 创建带缓冲channel

创建格式如下:

通道实例 := make(chan 通道类型,缓冲大小)

用法如下:

package main

import "fmt"

func main() {

   ch := make(chan int,3)

   fmt.Println("通道的大小为:",len(ch))

   ch<-1
   ch<-2
   ch<-3

   fmt.Println("发送元素到通道后通道的大小为:",len(ch))
}

输出为:

image.png

9.2 带缓冲channel与不带缓冲channel对比

缓冲channel非缓冲channel对比代码案例如下:

package main

import (
   "fmt"
   "time"
)

func main() {

   ch1 := make(chan int)
   fmt.Println("非缓冲通道",len(ch1),cap(ch1))
   go func() {
      data := <-ch1
      fmt.Println("获得数据",data)
   }()

   ch1<-100
   time.Sleep(time.Second)
   fmt.Println("赋值ok","main over...")

   ch2 := make(chan string)
   fmt.Println("非缓冲通道",len(ch2),cap(ch2))
   go sendData(ch2)
   for data := range ch2{
      fmt.Println("\t 读取数据",data)
   }
   fmt.Println("main over...")

   ch3 := make(chan string,6)
   fmt.Println("缓冲通道",len(ch3),cap(ch3))
   go sendData(ch3)
   for data:=range ch3{
      fmt.Println("\t 读取数据",data)
   }
   fmt.Println("main over...")
}

func sendData(ch chan string)  {
   for i:=1;i<=3;i++{
      ch<-fmt.Sprintf("data%v",i)
      fmt.Println("往通道放入数据:",i)
   }
   defer close(ch)

}

输出为:

image.png

10.单向channel

Go语言程序中可以在声明时约束其操作方向,如只发送或是只接收。这种约束方向的通道称为单向通道

声明格式如下:

只能发送通道: var 通道实例 chan<-元素类型

只能接收通道: var 通道实例 <-chan 元素类型

用法如下: Ch:=make(chan int)

只能发送通道: var read chan<-int =ch

只能接收通道: var recev <-chan =ch

如果你觉得这篇内容对你有帮助的话:

1、点赞支持下吧,让更多的人也能看到这篇内容

2、关注公众号:看那个码农,我们一起学习一起进步。