创建channel之后,我们可以使用channel进行数据收发操作。
使用channel发送(填充)数据
使用特殊的操作符<-
,把数据通过channel发送。
- 格式
通道变量 <- 值
通道变量是上文通过make创建的通道实例;值可以是变量、常量、表达式或函数返回值,值类型必须与make(chan 数据类型)
中的数据类型一致。
- 发送数据
在使用make创建channel后,就可以使用<-
向通道发送数据。
ch := make(chan interface{}) // 创建一个空接口通道
ch <- 0 // 把整型数据放入通道
ch <- "hello" // 把字符串放入通道
- 若数据未被接收,则发送数据将持续阻塞,直到数据被接收
把数据向通道发送,如果接收方一致没有接收,那么发送操作将持续阻塞,直到数据被接收方接收。
package main
func main() {
ch := make(chan int)
ch <- 0
}
// fatal error: all goroutines are asleep - deadlock!
go会智能发现一些永远无法发送成功的语句,并提示。上面的意思是,go运行时发现所有goroutine(包括main()函数的)都处于waiting状态,即所有goroutine并没有发送或接收数据对应的通道。
使用channel接收(取出)数据
通道接收(取出)数据的特性
- 通道收发数据需要在两个不同的goroutine之间进行
通过channel发送数据,如果通道中没有另外一个goroutine接收,数据发送方goroutine会持续阻塞,直到接收方goroutine接收数据;通过channel接收数据,如果通道中没有发送方goroutine发送数据,数据接收方goroutine将持续阻塞,直到发送方goroutine发送数据为止。
- 通道一次只能接收一个元素数据
通道接收(取出)数据的格式
一共有4种通道接收数据的写法。
- 阻塞模式接收数据
把接收变量作为<-
操作符的左值。执行此语句,发送方将会持续阻塞,直至接收方接收数据并赋值给data变量。
data := <-ch
- 非阻塞模式接收数据(高CPU占用,少用)
使用非阻塞方式从通道接收数据,语句不会阻塞。
data, ok := <-ch
data表示接收数据的通道变量,在没有接收到数据时,data为通道类型的零值。ok表示是否成功从通道接收到数据。
- 接收任意数据,忽略接收的数据
阻塞模式接收数据后,忽略从通道返回的数据。
<-ch
执行此语句,将会发生数据接收阻塞,直至接收到数据,但接收到的数据会被忽略。此方式使用channel在不同goroutine间阻塞收发,实现并发同步。
package main
import "fmt"
func main() {
ch := make(chan int) // 构建一个同步通道
go func() { // 开一个匿名函数的并发
fmt.Println("start goroutine")
ch <- 0 // 匿名函数快结束时,通过channel告知main()函数的goroutine,数据发送会一直阻塞,直到main()函数的goroutine接收为止。
fmt.Println("exit goroutine")
}()
fmt.Println("wait goroutine")
<-ch // 开启匿名函数的goroutine后,阻塞模式等待发送数据,接收任意数据后忽略
fmt.Println("all done")
}
/*
wait goroutine
start goroutine
exit goroutine
all done
*/
- 循环接收
通道数据可以使用for range
语句,进行多个元素的接收。遍历得到变量data。格式如下:
for data := range ch {
循环体
}
通道是可遍历的,遍历结果是接收到的数据,其数据类型是通道的元素类型。
package main
func main() {
ch := make(chan int) // 生成一个整型元素的通道
go func() { // 把匿名函数并发执行
for i := 3;i >= 0; i-- {
ch <- i // 使用通道发送值i
time.Sleep(time.Second) // 每次发送完暂停1秒
}
}()
for data := range ch { // 使用for range从通道中接收数据
fmt.Println(data) // 打印接收的变量
if data == 0 { // 当接收到0时,终止for range
break // 如果发送端继续发,接收goroutine已经推出,会报panic
}
}
}
/*
3
2
1
0
*/
使用goroutine和channel,实现并发打印
设计生产者与消费者模型:生产者是main函数的for循环,消费者是printer函数。整个模型使用两个goroutine:一个是main(),另一个是go printer创建的goroutine,两个goroutine通过第21行的通道进行通信。
这个通道有两个功能:
- 数据传送:第26行发送数据,第9行接收数据
- 控制指令:类似信号量的功能,同步goroutine操作。如第17行、29行、31行
package main
import "fmt"
// 定义打印函数
func printer(c chan int) {
for { // 启动一个死循环
data := <-c // 从函数入参channel中取得整型数据
if data == 0 {
break
}
fmt.Println(data) // 输出刚取得的整型数据
}
c <- 0 // 退出循环时,通知main函数(我搞定了)
}
func main() {
c := make(chan int) // 创建一个channel
go printer(c) // 并发执行,传入channel
for i := 1; i <= 10; i++ { // 使用for循环,把1~10通过通道c发送给printer函数的goroutine
c <- i
}
c <- 0 // 发完数据后,通知并发的printer结束循环(没数据了)
<-c // 阻塞式等待接收printer发送退出数据,然后main函数才能结束(搞定喊我),
}