Go语言 Channel

基本介绍

Channel 是 Go 中的一个核心类型,可以把它看成一个管道。
利用通道我们可以在多个 goroutine 之间传递数据。
如果说 Goroutine 是 Go 程序并发的执行体,Channel 就是它们之间的连接。
Channel 是可以让一个 Goroutine 发送特定值到另一个 Goroutine 的通信机制。
Channel 像一个传送带或者队列,总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序。

基本使用

声明

  1. 声明 channel: var 变量 chan 元素类型
  2. 声明的通道后需要使用 make 函数初始化之后才能使用
	var ch1 chan int   // 声明一个传递整型的通道
	var ch2 chan bool  // 声明一个传递布尔型的通道
	var ch3 chan string // 声明一个传递字符串的通道     
	ch1 := make(chan int) // 直接初始化一个无缓冲通道
	ch2 := make(chan int,2) // 初始化一个带缓冲的通道

操作

  1. 通道有发送(send)、接收(receive)和关闭(close)三种操作。
  2. 发送和接收都使用 <- 符号。

发送

  1. 使用 <- 符号放到通道变量右边,就可以把值发送到创建的通道中,看起来就像值流向通道中。
	ch1 := make(chan int ,1)
	ch1 <-1

接收

  1. 使用 <- 符号放到通道变量左边,就可以从通道中接收一个值,看起来就像通道中流出一个值到变量中。
	ch1 := make(chan int ,1)
	ch1 <-1
	result := <- ch1
	fmt.Println(result) // 1

关闭

使用 close 函数可以把通道关闭

	close(ch1)

关闭通道特点

虽然关闭通道看起来很简单,但是在实际场景中需要特别注意通道的关闭,如果不能正确通道,会引起一些意想不到的错误。
关闭后的通道有以下特点:

  1. 对一个关闭的通道再发送值就会导致 panic (demo1)。
  2. 关闭一个已经关闭的通道会导致 panic (demo2)
  3. 对一个关闭的通道进行接收会一直获取值直到通道为空 (demo3)。
  4. 对一个关闭的并且没有值的通道执行接收操作会得到对应类型的零值 (demo4)。

demo1

	func demo1(){
		ch1 := make(chan int ,1)
		close(ch1)
		ch1 <- 1  // panic: send on closed channel
	}

demo2

	func demo2(){
		ch1 := make(chan int ,1)
		close(ch1)
		close(ch1) // panic: close of closed channel
	}

demo3

	func demo3(){
		ch1 := make(chan int ,3)
		for i:=0;i<3;i++{
			ch1 <- i
		}
		close(ch1)
		for v := range ch1{
			fmt.Println(v)
		}
	}
  1. 如果这里不关闭 ch1 这个通道,下面的 for range 会一直去遍历 通道
  2. 没有值的话就会造成阻塞,在主协程里造成阻塞的话,就会造成死锁报以下致命错误
  3. 所以在使用 for range 去遍历通道的时候要注意关闭,或者在子协程里去这样使用。
	fatal error: all goroutines are asleep - deadlock!

demo4

	func demo4(){
		ch1 := make(chan int ,1)
		ch1 <-1
		close(ch1)
		fmt.Println(<-ch1)  //1
		fmt.Println(<-ch1) // 0
		result , ok := <- ch1
		fmt.Println(result) // 0
		fmt.Println(ok) //false
	}
  1. 通道关闭了是可以继续接收值的
  2. 如果有值则接收值,没有值则返回通道类型的零值
  3. 如果想知道通道是否关闭,可以接收第二个值,通过第二个值来判断通道是否关闭

无缓冲通道

通道有两种,无缓冲通道和带缓冲通道,先看下无缓冲通道。
无缓冲通道,发送者和接受者都要存在,有一方不存在会导致阻塞。
所以说无缓冲的通道又被称为阻塞的通道。

	ch := make(chan int)
	ch <- 10
	fmt.Println("发送成功")

以上代码会报错

	fatal error: all goroutines are asleep - deadlock!
	goroutine 1 [chan send]:

为什么会报错呢?
我们使用 ch := make(chan int)创建的是无缓冲的通道。
无缓冲的通道只有在有接收方接收值的时候才能发送值,但是在一个协程里执行到发送方或者接收方的时候就会阻塞,所以需要在两个协程间接收和发送。

代码调整

	func demo5(){
		ch := make(chan int)
		go func() {
			result := <- ch
			fmt.Println("接收成功",result)
		}()
		ch <- 10
		fmt.Println("发送成功")
	}
	func demo6(){
		ch := make(chan int)
		go func() {
			ch <- 10
			fmt.Println("发送成功")
		}()
		result := <- ch
		fmt.Println("接收成功",result)
	}

上面两种形式都可以,只要不要造成主协程阻塞就可以,主协程如果阻塞了就会报致命错误死锁。

缓冲通道

解决无缓冲通道(阻塞)死锁的问题,就是使用有缓冲的通道。通过缓存的使用,可以尽量避免阻塞,提高应用的性能。
带缓冲的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。
我们使用 make 函数在初始化的时候为其指定通道的容量(缓冲大小):
只要通道的容量大于零,那么该通道就是有缓冲的通道,通道的容量表示通道中能存放元素的数量。
我们可以使用内置的 len 函数获取通道内元素的数量,使用 cap 函数获取通道的容量。

	func demo7(){
		ch := make(chan int ,1)  // 创建一个容量为 1 的有缓冲区的通道
		ch <- 10
		fmt.Println("发送成功")
		result := <- ch
		fmt.Println("接收成功",result)
	}

这个例子中,创建了带1个缓冲的通道,所以往里面写值的时候没有造成阻塞。

	ch := make(chan int ,1)  // 创建一个容量为 1 的有缓冲区的通道
	ch <- 10 
	ch <- 10
	fmt.Println("发送成功")

这个例子中,第一次写值的时候可以,第二次写值的时候就会阻塞然后报死锁错误。

扇入和扇出

扇出/扇入模式是并发编程中常用的设计模式,特别是在 Go 语言中。它包括两个阶段:

  1. 扇出阶段,在这个阶段,单个 goroutine 将任务广播给多个工作 goroutine;
  2. 扇入阶段,在这个阶段,这些工作 goroutine 的结果被聚合到一个单一的通道中。

FAN - OUT 扇出

	func demo9() {
		taskChan := make(chan int, 10)
		quitChan := make(chan int,3)
	
		go func() {
			for i := 1; i <= 10; i++ {
				taskChan <- i
			}
			close(taskChan)
		}()
	
		go func() {
			for v := range taskChan{
				fmt.Println("work 1 处理任务:",v)
			}
			quitChan <-1
		}()
	
		go func() {
			for v := range taskChan{
				fmt.Println("work 2 处理任务:",v)
			}
			quitChan <-1
		}()
	
		go func() {
			for v := range taskChan{
				fmt.Println("work 3 处理任务:",v)
			}
			quitChan <-1
		}()
	
		<- quitChan
		<- quitChan
		<- quitChan
		fmt.Println("over")
	}

输出结果

	work 3 处理任务: 2
	work 3 处理任务: 4
	work 3 处理任务: 5
	work 3 处理任务: 6
	work 3 处理任务: 7
	work 3 处理任务: 8
	work 3 处理任务: 9
	work 3 处理任务: 10
	work 1 处理任务: 1
	work 2 处理任务: 3
	over

FAN - IN 扇入

func demo10(){
	taskChan := make(chan int, 10)
	quitChan := make(chan int,3)
	overChan := make(chan int)

	go func() {
		for i := 1; i <= 5; i++ {
			taskChan <- i
		}
		quitChan <-1
	}()

	go func() {
		for i := 5; i <= 10; i++ {
			taskChan <- i
		}
		quitChan <-1
	}()

	go func() {
		for v := range taskChan{
			fmt.Println("work  处理任务:",v)
		}
		fmt.Println("任务全部处理了")
		overChan <- 1
	}()

	<-quitChan
	<-quitChan
	close(taskChan)
	<-overChan
	fmt.Println("over")
}
	work  处理任务: 5
	work  处理任务: 6
	work  处理任务: 7
	work  处理任务: 8
	work  处理任务: 9
	work  处理任务: 10
	work  处理任务: 1
	work  处理任务: 2
	work  处理任务: 3
	work  处理任务: 4
	work  处理任务: 5
	任务全部处理了
	over
  • 19
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值