Go中的通道

如果说 goroutine 是 Go 程序并发的执行体,通道就是它们之间的连接。通道是可以让一个goroutine 发送特定值到另一个 goroutine 的通信机制。每一个通道是一个具体类型的导管,叫做通道的元素类型。一个有 int 类型元素的通道写为 chan int。

ch := make(chan int)  // ch 的类型是 'chan int'

像 map 一样,通道是一个使用 make 创建的数据结构的引用。当复制或者作为参数传递到一个函数时,复制的是引用,这样调用者和被调用者都引用同一份数据结构。和其他引用类型一样,通道的零值是 nil。
同种类型的通道可以用 == 符号就行比较。当二者都是同一通道数据的引用时,比较值为 true 。通道可以和 nil 进行比较。
通道有两个主要操作:发送(send)和接收 (receive),两者统称为通信。send 语句从一个goroutine 传输一个值到另一个在执行接收表达式的 goroutine。

	ch <- x    // 发送语句
	x = <-ch   // 赋值语句中的接收表达式
	<-ch       // 接收语句,丢弃结果

通道支持第三个操作:关闭(close),它设置一个标志位来指示值当前已经发送完毕,这个通道后面没有值了;关闭后的发送操作将导致宕机。在一个已经关闭的通道上进行接收操作,将获取所有已经发送的值,直到通道为空;这时任何接收操作会立即完成,同时获取到一个通道元素类型对应的零值。

无缓冲通道

无缓冲通道上的发送操作将会阻塞,直到另一个 goroutine 在对应的通道上执行接收操作,这时值传送完成,两个 goroutine 都可以继续执行。相反,如果接收操作先执行,接收方 goroutine 将阻塞,直到另一个 goroutine 在同一个通道上发送一个值。
使用无缓冲通道进行通信导致发送和接收 goroutine 同步化。因此,无缓冲通道也称为同步通道。

管道

通道可以用来连接 goroutine,这样一个输出是另一个的输入。这个叫管道。
在通道关闭后,任何后续的发送操作将会导致应用崩溃。当关闭的通道被读完(就是最后一个发送的值被接收)后,所有后续的接收操作顺畅进行,只是获取到的是零值。
没有一个直接的方式来判断通道是否已经关闭,但是这里有接收操作的一个变种,它产生两个结果:接收到的通道元素,以及一个布尔值(通常称为 ok),它为 true 的时候代表接收成功,false 表示当前的接收操作在一个关闭的并且读完的通道上。

	// squarer
	go func() {
		for {
			x , ok := <-naturals
			if !ok {
				break
			}
			squarer <- x * x
		}
		close(squarer)
	}()

单向通道类型

类型 chan <- int 是一个只能发送的通道,允许发送但不允许接收。反之,类型 <-chan int 是一个只能接收的 int 类型通道,允许接收但是不能发送。(<- 操作符相当于 chan 关键字的位置是一个帮助记忆的点)。
任何赋值操作中将双向通道转换为单向通道都是允许的,但是反过来是不行的。

缓冲通道

ch := make(chan string, 3)

缓冲通道的发送操作在队列的尾部插入一个元素,接收操作从队列的头部移除一个元素。如果通道满了,发送操作会阻塞所在的 goroutine 直到另一个 goroutine 对它进行接收操作来留出可用的空间。反过来,如果通道是空的,执行接收操作的 goroutine 阻塞,直到另一个 goroutine 在通道上发送数据。
新手有时候粗暴地将缓冲通道作为队列在单个 goroutine 中使用,但是这是一个错误。通道和 goroutine 的调度深度关联,如果没有另一个 goroutine 从通道进行接收,发送者(也许是整个程序)有被永久阻塞的风险。如果仅仅需要一个简单的队列,使用 slice 创建一个就可以了。

使用 select 多路复用

有时候不能只从一个通道上接收,因为哪一个操作都会在完成前阻塞。所以需要多路复用的操作,需要一个 select 语句:

	select {
	case <-ch1:
		// .... 
	case x :=<-ch2:	
		// ..... 
	case ch3 <- y:		
		// ....
	default:			
		// ....
	}

以上是 select 语句的通用形式。像 switch 语句一样,它有一系列的情况和一个可选的默认分支。每一个情况都指定一次通信(在一些通道上进行发送或接收操作)和关联的一段代码块。接收表达式操作可能出现在它本身上,像第一个情况,或者在一个短变量声明中,像第二个情况;第二种形式可以让你引用所接收的值。
select 一直等待,直到一次通信来告知一些情况可以执行。然后,它进行这次通信,执行此情况所对应的语句;其他的通信将不会发生。对于没有对应情况的 select,select{} 将永远等待。

	ch := make(chan int, 1)
	for i := 0; i < 10; i++ {
		select {
		case x := <-ch:
			fmt.Println(x) // 0 2 4 6 8
		case ch <- i:
		}
	}

如果多个情况同时满足,select 会随机选择一个,这样保证每一个通道有相同的机会被选中。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值