Go语言中的channel

Go语言中的channel

Don’t communicate by sharing memory, share memory by communicating.

上面这句话是Go语言的主要创造者之一Rob Pike的至理名言,这也体现了Go语言最重要的编程理念。

goroutine运行在相同的地址空间,因此访问共享内存必须做好同步。引用类型channel是CSP模式的具体体现,用于多个goroutine之间的通信,其内部实现了同步,确保并发安全。

 

channel

channel类型的值本身就是并发安全的,这也是Go语言自带的、唯一一个可以满足并发安全性的类型。

 

声明并初始化channel

Go语言中channel的关键字是chan,声明、初始化channel的语法如下:

var 通道变量 chan 通道类型
var 通道变量 chan <- 通道类型	// 单向channel,只写
var 通道变量 <- chan 通道类型	// 单向channel,只读

通道变量 := make(chan Type)	// 无缓存channel
通道变量 := make(chan Type, 0)	// 无缓存channel,与上面等价
通道变量 := make(chan Type, capacity int)	// 有缓存channel, capacity > 0

通道变量是保存通道的引用变量;通道类型是指该通道可传输的数据类型。

当capacity为0时,channel是无缓冲阻塞读写的;当capacity大于0时,channel是有缓冲、非阻塞的,直到写满capacity个元素才阻塞写入。

 

操作channel的语法

一个通道相当于一个先进先出(FIFO)的队列。也就是说,通道中的各个元素值都是严格地按照发送的顺序排列的,先被发送到通道的元素值一定会先被接收。

元素值的发送和接收都需要用到操作符<-。我们也可以叫它接送操作符。一个左尖括号紧接着一个减号形象地代表了元素值的传输方向。

ch := make(chan Type, capacity int)

ch <- value	// 发送value到ch
<-ch	// 从ch中接收一个值并将其丢弃
x := <-ch	// 从ch中接收一个数据,并赋值给x
x, ok := <-ch	// 同上,并检查通道是否关闭或通道中是否还有值,将此状态赋值给ok

所下示例:

package main

import "fmt"

func main() {
	ch := make(chan int, 3)
	ch <- 2
	ch <- 1
	ch <- 3
	elem := <-ch
	fmt.Printf("The first element received from channel ch: %v\n", elem)
}

运行结果如下:

在这里插入图片描述
 

缓冲机制

channel按是否支持缓冲区可分为无缓冲的通道(unbuffered channel)和有缓冲的通道(buffered channel)。

 

无缓冲通道

无缓冲的通道是指在接收前没有能力保存任何值的通道。这种类型的通道要求发送goroutine和接收goroutine同时准备好,才能完成发送和接收操作。如果两个goroutine没有同时准备好,会导致先执行发送或接收操作的goroutine阻塞等待。
这种对通道进行发送和接收的交互行为本身就是同步的,其中任意一个操作都无法离开另一个操作单独存在。

 

简单无缓冲通道代码示例如下:

package main

import (
   "fmt"
)

func main() {
   ch := make(chan int)
   go func() {
      fmt.Println("进入goroutine")
      // 添加一个内容后控制台输出:1 true
      //ch<-1

      //关闭ch控制台输出:0 false
      close(ch)
   }()
   c, d := <-ch 
   fmt.Println(c, d)
   fmt.Println("程序执行结束")
}
  1. 上面代码如果没有从channel中取值,即不执行c, d := <-ch语句,程序结束时go func还来不及执行。
  2. 上面代码演示了同步操作,类似与WaitGroup功能,保证程序结束时goroutine已经执行完成。
  3. 向goroutine中添加内容的代码会阻塞goroutine执行,所以要把ch <- 1放到goroutine有效代码最后一行。
  4. 无论是向channel存数据还是取数据都会阻塞。
  5. close(channel)关闭channel,关闭后只可读不可写。

 

有缓冲通道

有缓冲通道是一种在被接收前能存储一个或多个值的通道。

这种类型的通道并不强制要求goroutine之间必须同时完成接收和发送。通道阻塞发送和接收的条件也会不同。只有在通道中没有要接收的值时,接收动作才会阻塞。只有在通道没有可用缓冲区容纳被发送的值时,发送动作才会阻塞。
这导致有缓冲的通道和无缓冲的通道之间有一个很大的不同:无缓冲的通道保证进行发送和接收的goroutine会在同一时间进行数据交换,有缓冲的通道没有这种保证。

由于存在缓冲区,在缓冲区未填满的情况下,程序就不会被阻塞执行。

package main

import "fmt"

func main() {
   ch := make(chan int, 3) //缓冲区大小为3,里面消息个数小于等于3时都不会阻塞goroutine
   ch <- 1
   fmt.Println(<-ch)
   ch <- 2
   fmt.Println(<-ch)
   ch <- 3
   ch <- 4
   fmt.Println(len(ch))//输出2,表示channel中有两个消息
   fmt.Println(cap(ch))//输出3,表示缓存大小总量为3
}

 

close和range

当发送者知道没有更多的值需要发送到channel时,让接收者也能及时知道没有更多的值需要接收是很有必要的,因为这样就可以让接收者停止不必要的等待。

这可以通过内置的close函数和range关键字来实现。

 

使用close关闭channel时需要注意:

  1. channel不像文件一样需要经常去关闭,只有当你确实没有任何需要发送的数据时,或者想要显式地结束range循环之类的,才会去关闭channel。
  2. 关闭channel后,无法向channel再次发送数据,再次发送将会引发panic错误。
  3. 关闭channel后,可以继续从channel接收数据。
  4. 对于nil channel,无论接收还是发送都会被阻塞。
package main

import (
	"fmt"
)

func main() {
	ch := make(chan int, 3)
	go func() {
		for i := 0; i < 3; i++ {
			fmt.Printf("len = %v, cap = %v\n", len(ch), cap(ch))
			ch <- i
		}
		close(ch)
	}()

	for {
		if val, ok := <-ch; ok == true {
			fmt.Println(val)
		} else {
			return
		}
	}
}

发送者在发送完数据后,使用了close关闭channel。channel被关闭后,ok的值就会变为false,最终程序结束运行。运行结果如下:

在这里插入图片描述

 

除了使用上面的方式来遍历channel,还可以使用更加简洁的range关键字。如下所示:

	for data := range ch{
		fmt.Println(data)
	}

当channel关闭后,range也能自动结束本次遍历。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值