Channel相关学习

73 阅读5分钟

程序间的并发通信

并发程序间的最大问题是通信,而最常见的并发通信模型是 共享内存 和 消息机制。

1、共享内存(Shared Memory)

  • 是指多个并发单位分别保存对同一数据的引用,实现对该数据的共享;
  • 多个并发单位在同时访问共享内存时,必须使用互斥锁等相关机制,以保证对共享内存的互斥访问。

2、消息机制(Message Communication Mechanism)

  • Go语言中,是以消息机制作为主要通信方法;
  • 消息机制规定每个并发单位是自包含的、独立的个体,并且都有自己的变量,这些变量不能在不同的并发单位之间共存。
  • 每个并发单位的输入、输出只有一种,就是消息;
  • 不同进程间靠消息进行通信,而不会共享内存数据。

不同 goroutine 之间如何通信

1、全局变量的互斥锁

因未对全局变量加锁,所以会出现资源争夺问题,代码会出现错误

解决方案:通过 sync包 加入互斥锁 Mutex

sync包提供了基本的同步的基元,如互斥锁。除了Once和WaitGroup类型,大部分都是适用于低水平程序线程,高水平的同步使用channel通信更好一些。

 package main
 ​
 import (
     "fmt"
     "sync"
     "time"
 )
 ​
 //使用goroutine,计算1-20的阶乘,并把各个数的阶乘放入map中
 //思路:
 //1.编写一个函数,来计算各个数的阶乘,并放入map中
 //2.启动多个协程,统计的结果放入到map中
 //3.map应该做出一个全局的
 ​
 var (
     myMap = make(map[int]int, 10)
     //声明一个全局的互斥锁
     lock sync.Mutex
 )
 ​
 func test(n int){
     res := 1
     for i := 1; i<=n; i++ {
         res *= i
     }
     
     //这里将res放入到myMap
     //加锁
     lock.Lock()
     myMap[n] = res
     //解锁
     lock.Unlock()
 }
 ​
 func main(){
     //开启多个协程完成任务
     for i := 1; i<=20; i++{
         go test(i)
     }
     
     //休眠5秒钟
     time.Sleep(time.Second * 5)
     
     //输出结果
     lock.Lock()
     for i, v := range myMap {
         fmt.Printf("map[%d]=%d\n", i, v)
     }
     lock.Unlock()
 }

Channel

  • 通道(channel)是用来传递数据的一个数据结构——队列。
  • 通道可用于两个或多个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。
  • channel 中数据是先进先出(FIFO)
  • channel 中数据的发送和接收都是原语操作,不会中断,只会失败。

Channel的声明和初始化

和一般变量声明基本相同,只是在类型前加关键字 “chan”:

 var chanName chan ElementType

ElementType:指定Channel所能传递的数据类型

还可以使用 "make()" 函数直接声明和初始化:

 ch := make(chan int)

根据数据交换行为,分无缓冲通道和缓冲通道

无缓冲通道: 用于执行goroutine之间的同步通信

缓冲通道: 用于执行异步通信

无缓冲通道保证在发送和接收发生的瞬间执行两个goroutine之间的交换;缓存通道没有这样的保证。

 Unbuffered := make(chan int)    //整型无缓冲通道
 buffered := make(chan int, 10)  //整型有缓冲通道

数据的接受和发送

操作符 <- 用于指定通道的方向,发送或接收。

如果未指定方向,则为双向通道。

将一个数据发送(写入)至Channel:

 ch <- value

向Channel写入数据通常会导致程序阻塞(Block),直到有其他Goroutine从这个Channel中读取数据。

从Channel中接收(读取)数据:

 value := <- ch

也可用以下语法取出数据丢弃:

 <- ch

如果Channel之前没有写入数据,那么从Channel中读取数据也会导致阻塞,直到Channel中被写入数据为止。

实例:

 package main
 ​
 import (
     "fmt"
     "math/rand"
 )
 ​
 func Test(ch chan int) {
     ch <- rand.Int() //向 Channel 中写入一个随机数
     fmt.Println("Go...")
 }
 ​
 func main() {
     chs := make([]chan int, 10)
     for i := 0; i < 10; i++ {
         chs[i] = make(chan int)
         go Test(chs[i]) //启动10个Goroutine
     }
     for _, ch := range chs {
         value := <-ch //阻塞等待退出信号
         fmt.Println(value)
     }
 }

运行结果:

 Go...
 7188982543361109100
 6951295209423991719
 4522349878514052167
 7843658900962395408
 6975911838343108630
 8201548578515923750
 1820885331502800729
 5307814057134748065
 5747925000352368897
 6425996854107296150

channel 的关闭

使用内置函数 close() 可以关闭 channel,当 channel 关闭后,就不能向 channel 写数据,但仍然可以从该 channel 读取数据。

判断channel是否被关闭,可以在读取channel的时候使用多重返回值的方式:

value, ok := <- ch

如果返回值是 false 则表示channel已被关闭。

channel 的遍历

channel 支持 for-range 的方式进行遍历

注意:

1、在遍历时,如果 channel 没有关闭,则会出现 deadlock 的错误;

2、在遍历时,如果 channel 已经关闭,则会正常遍历数据,遍历完成后会退出遍历。

//放入10个数据到通道中
intChan := make(chan int, 10)
for i := 0; i<100; i++ {
	intChan <- i*2
}

//遍历通道channel,前提需要关闭通道
close(intChan)
for v := range intChan {
    fmt.Println("v=%d\n",v)
}

单向channel

可将channel指定为单向通道,即只能发送,或只能接收。

只能发送的channel变量的定义:

var chanName <- chan ElementType

只能接收的channel变量的定义:

var chanName chan <- ElementType

在定义了单向channel后,还要对其初始化。

单向channel也可由一个已定义的双向(正常)channe转换而来:

ch := make(chan int)
chRead := <- chan int(ch)
chWrite := chan <- int(ch)

异步channel

对于在goroutine间传输大量数据的应用,可以使用异步通道,从而达到消息队列的效果。

异步channel,就是给channel设定一个Buffer值。

在Buffer 未写满 / 未读完 的情况下,不阻塞 发送 / 接收 操作。

在调用make()函数时,将缓冲区的大小作为第二个参数传入即可,例如:

ch := make(chan int, 1024)