创建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接收,数据发送方goroutine会持续阻塞,直到接收方goroutine接收数据;通过channel接收数据,如果通道中没有发送方goroutine发送数据,数据接收方goroutine将持续阻塞,直到发送方goroutine发送数据为止。
一共有4种通道接收数据的写法。
把接收变量作为<-
操作符的左值。执行此语句,发送方将会持续阻塞,直至接收方接收数据并赋值给data变量。
data := <-ch
使用非阻塞方式从通道接收数据,语句不会阻塞。
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 */
设计生产者与消费者模型:生产者是main函数的for循环,消费者是printer函数。整个模型使用两个goroutine:一个是main(),另一个是go printer创建的goroutine,两个goroutine通过第21行的通道进行通信。
这个通道有两个功能:
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函数才能结束(搞定喊我), }