本文介绍如何使用Golang通道。通道是Go应用中链接协程通信的管道,协程可以往通道中推入值或从中读取值。利用通道可以非常方便地实现高性能、高并发应用,相比与其他语言更简单,这并不是巧合,而是Go语言的设计理念————并发作为语言的一等公民,使得并发应用尽可能简单而不失灵活性。
通道的思想起始很早就有了,但Go的实现者希望通道承担更多使命————以尽可能简单的方式让开发者创建更好、更清晰的高性能并发应用。确实,Go语言最吸引我的能力是可以轻松创建并发应用,到目前为止,不得不说这是一种乐趣。
1. 从简单示例开始
我们首先从简单示例开始。创建一个函数负责计算生成一个随机数并传给通道:
package main
import (
"fmt"
"math/rand"
)
func CalculateValue(values chan int) {
// 设置随机种子,避免随机函数生成相同的值
rand.Seed(time.Now().UnixNano())
value := rand.Intn(10)
fmt.Println("计算随机值: {}", value)
// 往通道发送值
values <- value
}
func main() {
fmt.Println("Golang Channel 教程")
// 创建int类型通道,只能传入int类型值
values := make(chan int)
defer close(values)
go CalculateValue(values)
// 从通道接收值
value := <-values
fmt.Println(value)
}
values := make(chan int)valuesCalculateValue
make
defer close(values)CalculateValue(values)
value := <-values
注:当我们执行该程序时,程序并没有立刻结束。这是因为给通道发送值或从通道接收值都会阻塞当前协程。main函数被阻塞直到从通道中接收到值为止。
执行代码,查看运行结果:
Go Channel Tutorial
Calculated Random Value: {} 1
1
我们看到实例化并使用通道非常直接、简单,下面继续看稍微复杂的场景。
2. 无缓冲通道
CalculateValue()
go CalculateValue(valueChannel)
package main
import (
"fmt"
"math/rand"
"time"
)
func CalculateValue(c chan int) {
// 设置随机种子,避免随机函数生成相同的值
rand.Seed(time.Now().UnixNano())
value := rand.Intn(10)
fmt.Println("计算随机值: {}", value)
time.Sleep(1000 * time.Millisecond)
c <- value
fmt.Println("仅当另一个协程执行从通道中取值之后才执行")
}
func main() {
fmt.Println("Golang Channel 教程")
// 创建int类型通道,只能传入int类型值
valueChannel := make(chan int)
defer close(valueChannel)
go CalculateValue(valueChannel)
go CalculateValue(valueChannel)
// 从通道接收值
values := <-valueChannel
fmt.Println(values)
}
执行程序,我们仅看到第一个协程执行了最后一行输出语句:
Golang Channel 教程
计算随机值: {} 4
仅当另一个协程执行从通道中取值之后才执行
4
c <- value
3. 缓存通道
绕过这种阻塞行为的方法是使用缓冲通道。缓存通道本质是使用给定大小的队列实现跨协程通信。创建缓存通道需要make函数中指定容量:
bufferedChannel := make(chan int, 2)
c <- value
我们修改上面的程序使用缓存通道,同时在main函数中增加等待语句,确保main函数最后完成。
func CalculateValue(c chan int) {
// 设置随机种子,避免随机函数生成相同的值
rand.Seed(time.Now().UnixNano())
value := rand.Intn(10)
fmt.Println("计算随机值: {}", value)
time.Sleep(1000 * time.Millisecond)
c <- value
fmt.Println("仅当另一个协程执行从通道中取值之后才执行")
}
func main() {
fmt.Println("Golang Channel 教程")
// 创建int类型通道,只能传入int类型值
valueChannel := make(chan int, 2)
defer close(valueChannel)
go CalculateValue(valueChannel)
go CalculateValue(valueChannel)
// 从通道接收值
values := <-valueChannel
fmt.Println(values)
time.Sleep(1000 * time.Millisecond)
}
现在执行程序应该能够看到第二个协程继续执行,无论通道中的值是否被接收。通过对比我们看到两种通道的差异:非缓存通道默认阻塞,而缓存通道当没有满时不阻塞。
输出结果:
Golang Channel 教程
计算随机值: {} 6
仅当另一个协程执行从通道中取值之后才执行
6
仅当另一个协程执行从通道中取值之后才执行
4. 总结
本文我们讨论了Golang的通道,了解两种通道的差异以及如何在Golang并发应用中使用。