通道
1635字约5分钟
2025-02-03
通道Channels
通道(channels) 是连接多个协程的管道。 你可以从一个协程将值发送到通道,然后在另一个协程中接收。
这个演示demo涵盖了通道的基本概念,包括创建通道、发送值到通道、从通道接收值以及有缓冲通道的使用。
package main
import "fmt"
func main() {
// 创建一个无缓冲的通道
ch := make(chan int)
// 启动一个 goroutine 将值发送到通道
go func() {
ch <- 10
}()
// 从通道接收值并打印
value := <-ch
fmt.Println("Received value from channel:", value)
// 创建一个有缓冲的通道
bufferedCh := make(chan string, 3)
// 将值发送到有缓冲的通道
bufferedCh <- "Hello"
bufferedCh <- "from"
bufferedCh <- "channel"
// 从有缓冲的通道接收值并打印
fmt.Println(<-bufferedCh)
fmt.Println(<-bufferedCh)
fmt.Println(<-bufferedCh)
}
通道同步
在这个演示demo中,我们首先创建了一个用于通知任务完成的通道done,并在worker函数内部模拟了一个耗时的任务。在worker函数中,任务执行完毕后,我们向done通道发送了一个完成信号。在main函数中,我们启动了一个goroutine执行任务,并在主线程中等待从通道中接收到完成信号。一旦接收到完成信号,我们打印出“work completed”
package main
import (
"fmt"
"time"
)
func worker(done chan bool) {
fmt.Print("working...")
time.Sleep(time.Second)
fmt.Println("done")
// 向通道发送完成信号
done <- true
}
func main() {
// 创建一个用于通知任务完成的通道
done := make(chan bool)
// 启动一个 goroutine 执行任务
go worker(done)
// 等待任务完成,阻塞直到从通道中接收到完成信号
<-done
fmt.Println("work completed")
}
通道方向
当使用通道作为函数的参数时,你可以指定这个通道是否为只读或只写。 该特性可以提升程序的类型安全。
package main
import "fmt"
// 通道只能用于发送数据的函数(只写)
func sendData(ch chan<- int, data int) {
fmt.Println("Sending", data)
ch <- data
}
// 通道只能用于接收数据的函数(只读)
func receiveData(ch <-chan int) {
data := <-ch
fmt.Println("Received", data)
}
func main() {
// 创建一个双向通道
ch := make(chan int)
// 将通道通过不同函数的参数限制为发送或接收操作
go sendData(ch, 10)
go receiveData(ch)
// 等待goroutine执行完毕后打印输入的内容
var input string
fmt.Scanln(&input)
fmt.Println(input)
}
通道选择器
Go 的 选择器(select) 让你可以同时等待多个通道操作。 将协程、通道和选择器结合,是 Go 的一个强大特性。
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
// 创建两个通道,用于模拟两个并发的任务
ch1 := make(chan string)
ch2 := make(chan string)
// 启动第一个并发任务
go func() {
time.Sleep(2 * time.Second) // 模拟任务执行时间
ch1 <- "任务1完成" // 将任务1的结果发送到通道ch1
}()
// 启动第二个并发任务
go func() {
time.Sleep(1 * time.Second) // 模拟任务执行时间
ch2 <- "任务2完成" // 将任务2的结果发送到通道ch2
}()
// 使用通道选择器选择首先到达的任务
for i := 0; i < 2; i++ {
select {
case result := <-ch1: // 从通道ch1接收结果
fmt.Println("ch1接收到结果:", result)
case result := <-ch2: // 从通道ch2接收结果
fmt.Println("ch2接收到结果:", result)
}
}
// 主函数继续执行其他操作
useTime := time.Since(now)
fmt.Println("程序运行时间:", useTime)
}
超时处理
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
// 创建一个通道,用于接收结果
ch := make(chan string)
// 启动一个并发任务
go func() {
// 模拟任务执行时间,这里设置为3秒
time.Sleep(3 * time.Second)
// 将任务结果发送到通道中
ch <- "任务完成"
}()
// 使用 select 语句等待任务结果,设置超时时间为2秒
select {
case result := <-ch: // 如果在2秒内接收到了任务结果
fmt.Println(result)
case <-time.After(2 * time.Second): // 如果超过了2秒仍未接收到任务结果
fmt.Println("任务超时")
}
// 主函数继续执行其他操作
fmt.Println("程序运行时间:", time.Since(now))
fmt.Println("主函数继续执行...")
}
非阻塞通道操作
常规的通过通道发送和接收数据是阻塞的。 然而,我们可以使用带一个 default 子句的 select 来实现 非阻塞 的发送、接收,甚至是非阻塞的多路 select。
package main
import (
"fmt"
"time"
)
func main() {
// 创建一个字符串通道
ch := make(chan string)
// 启动一个并发任务,向通道发送数据
go func() {
time.Sleep(2 * time.Second) // 模拟任务执行时间
ch <- "任务完成" // 将任务结果发送到通道中
}()
// time.Sleep(3 * time.Second)
// 使用 select 语句进行非阻塞通道操作
select {
case result := <-ch: // 尝试从通道中接收数据
fmt.Println(result)
default:
fmt.Println("暂无任务结果")
}
// 主函数继续执行其他操作
fmt.Println("主函数继续执行...")
}
通道的关闭
关闭 一个通道意味着不能再向这个通道发送值了。 该特性可以向通道的接收方传达工作已经完成的信息。
package main
import (
"fmt"
)
func main() {
// 创建一个整型通道
ch := make(chan int)
// 启动一个并发任务,向通道发送数据
go func() {
for i := 1; i <= 5; i++ {
fmt.Println("发送数据:", i)
ch <- i // 将数据发送到通道中
}
// 发送完数据后关闭通道
close(ch)
fmt.Println("数据发送完毕,通道关闭")
}()
// 从通道中循环接收数据,直到通道关闭
for {
// 尝试从通道中接收数据
num, ok := <-ch
// 如果通道已关闭并且没有数据可接收,则退出循环
if !ok {
fmt.Println("通道已关闭,没有数据可接收")
break
}
// 打印接收到的数据
fmt.Println("接收到数据:", num)
}
// 主函数继续执行其他操作
fmt.Println("主函数继续执行...")
}
通道遍历
在前面的章节我们讲过 for 和 range 为基本的数据结构提供了迭代的功能。 我们也可以使用这个语法来遍历的从通道中取值。
package main
import (
"fmt"
)
func main() {
// 创建一个整型通道
ch := make(chan int)
// 启动一个并发任务,向通道发送数据
go func() {
for i := 1; i <= 5; i++ {
ch <- i // 将数据发送到通道中
}
// 关闭通道
close(ch)
}()
// 使用 for 循环遍历通道,接收数据并打印
for num := range ch {
fmt.Println("接收到数据:", num)
}
// 主函数继续执行其他操作
fmt.Println("主函数继续执行...")
}