go 语言通道 channel 基础( 二 )

使用通道接收数据

通道接收同样使用<-操作符

通道接收有如下特性

① 通道的收发操作在不同的两个 goroutine 间进行。

由于通道的数据在没有接收方处理时,数据发送方会持续阻塞,因此通道的接收必定在另外一个 goroutine 中进行。

② 接收将持续阻塞直到发送方发送数据。

如果接收方接收时,通道中没有发送方发送数据,接收方也会发生阻塞,直到发送方发送数据为止。

③ 每次接收一个元素。

通道一次只能接收一个数据元素。

通道的数据接收的 4 种写法

1 阻塞接收数据

阻塞模式接收数据时,将接收变量作为<-操作符的左值,格式如下:

data := <-ch

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

2 非阻塞接收数据

使用非阻塞方式从通道接收数据时,语句不会发生阻塞,格式如下:

data, ok := <-ch

data:表示接收到的数据。未接收到数据时,data 为通道类型的零值。

ok:表示是否接收到数据。

非阻塞的通道接收方法可能造成高的 CPU 占用,因此使用非常少。如果需要实现接收超时检测,可以配合 select 和计时器 channel 进行,可以参见后面的内容。

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

阻塞接收数据后,忽略从通道返回的数据,格式如下:

<-ch

执行该语句时将会发生阻塞,直到接收到数据,但接收到的数据会被忽略。这个方式实际上只是通过通道在 goroutine 间阻塞收发实现并发同步。

使用通道做并发同步的例子:

package main

func main() {
	ch := make(chan int)
	go func() {
		for i := 1; i <= 10; i++ {
			println(i)
		}
		ch <- 1
	}()
	println("main done")
	// 利用接受产生阻塞
	<-ch
}

4 循环接收

通道的数据接收可以借用 for 语句进行多个元素的接收操作,示例代码 :

package main

import (
	"time"
)

func main() {
	// 创建管道
	ch := make(chan int)
	// 协程取值
	go func() {
		for {
			data := <-ch
			if data == -1 {
				break
			}
			println("取出", data)
		}
	}()
	// 向管道发送数据
	for i := 1; i <= 10; i++ {
		println("放入 :", i)
		ch <- i
		// 每次发送完时等待
		time.Sleep(time.Second)
	}
	// 通知读取协程退出
	ch <- -1

	println("main done")
}

使用 for range 遍历管道

package main

func main() {
	// 创建管道
	ch := make(chan int)
	// 启动一个goroutine 向管道内写入数据
	go func() {
		for i := 0; i < 10; i++ {
			ch <- i
		}
		// close 管道
		close(ch)
	}()
	//
	for data := range ch {
		println(data)
	}

	println("main end")
}

注意关闭管道避免锁死.