深色模式
Go语言之通道(Channel)
什么是通道
通道(Channel) 是Go语言中用于在多个goroutine之间传递数据的管道。通道具有以下特性:
- 类型安全:通道只能传递一种特定类型的数据。
- 线程安全:通道的操作是原子性的,不需要额外的锁机制。
- 通信顺序:发送和接收操作遵循先进先出的顺序。
通道的主要作用是在线程之间传递消息,实现同步和数据共享。
创建通道
使用内置的 make
函数创建通道:
go
ch := make(chan 数据类型)
示例:
go
// 创建一个传递整数的通道
intChan := make(chan int)
// 创建一个传递字符串的通道
stringChan := make(chan string)
发送和接收数据
- 发送数据:使用
ch <- value
将数据发送到通道。 - 接收数据:使用
value := <- ch
从通道接收数据。
示例:
go
go func() {
intChan <- 42 // 发送整数 42 到通道
}()
value := <- intChan // 从通道接收整数
fmt.Println(value) // 输出: 42
注意:
- 发送和接收操作会阻塞,直到另一端准备好。
- 发送操作阻塞,直到接收者接收到数据。
- 接收操作阻塞,直到发送者发送数据。
无缓冲通道与缓冲通道
无缓冲通道
- 定义:没有缓冲区,发送和接收必须同步完成。
- 特性:发送者和接收者必须同时准备好,才能完成数据传递。
示例:
go
ch := make(chan int) // 无缓冲通道
缓冲通道
- 定义:具有指定容量的缓冲区,可以存储一定数量的数据。
- 特性:
- 当缓冲区未满时,发送操作不会阻塞。
- 当缓冲区为空时,接收操作会阻塞。
创建缓冲通道:
go
ch := make(chan 数据类型, 缓冲容量)
示例:
go
ch := make(chan int, 5) // 缓冲通道,容量为5
示例用法:
go
ch := make(chan int, 2)
ch <- 1 // 不阻塞
ch <- 2 // 不阻塞
// ch <- 3 // 阻塞,因为缓冲区已满
fmt.Println(<-ch) // 输出: 1
fmt.Println(<-ch) // 输出: 2
通道的方向
通道可以指定为只发送或只接收,用于限制函数对通道的操作,提高代码的安全性和可读性。
只发送通道
go
chan<- 数据类型
示例:
go
func sendData(ch chan<- int) {
ch <- 100 // 只能发送,不能接收
}
只接收通道
go
<-chan 数据类型
示例:
go
func receiveData(ch <-chan int) {
data := <-ch // 只能接收,不能发送
fmt.Println(data)
}
通道的关闭
使用内置函数 close
关闭通道,表示不再向通道发送数据。
go
close(ch)
注意:
- 关闭通道后,再发送数据会导致
panic
。 - 接收数据时,如果通道已关闭且缓冲区为空,接收操作会返回零值。
检测通道是否关闭:
go
value, ok := <-ch
if !ok {
fmt.Println("通道已关闭")
}
使用 select
语句
select
语句用于在多个通道操作中进行选择,类似于 switch
,但用于通道的并发操作。
语法:
go
select {
case <-ch1:
// 从 ch1 接收到数据
case ch2 <- value:
// 向 ch2 发送数据
default:
// 如果没有可用的通道操作,执行默认分支
}
示例:
go
select {
case msg := <-msgChan:
fmt.Println("收到消息:", msg)
case sig := <-sigChan:
fmt.Println("收到信号:", sig)
default:
fmt.Println("没有可用的通道操作")
}
通道的范围循环
使用 for ... range
可以遍历从通道接收到的数据,直到通道被关闭。
示例:
go
ch := make(chan int)
go func() {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch)
}()
for value := range ch {
fmt.Println("收到:", value)
}
输出:
收到: 0
收到: 1
收到: 2
收到: 3
收到: 4
通道的阻塞与同步
阻塞行为
- 发送阻塞:发送者在缓冲区已满或无缓冲通道时等待接收者。
- 接收阻塞:接收者在缓冲区为空或无缓冲通道时等待发送者。
使用通道进行同步
通道可以用于在goroutine之间进行同步,确保某些操作在特定时机发生。
示例:
go
done := make(chan bool)
go func() {
fmt.Println("任务开始")
time.Sleep(2 * time.Second)
fmt.Println("任务完成")
done <- true // 发送信号,表示完成
}()
<-done // 等待任务完成
fmt.Println("所有任务已完成")
通道状态与操作总结
通道状态 \ 操作 | 读取 (<- ch ) | 写入 (ch <- ) | 关闭 (close(ch) ) |
---|---|---|---|
有缓冲 | 缓冲区非空:成功读取 缓冲区为空:阻塞 | 缓冲区未满:成功写入 缓冲区已满:阻塞 | 正常关闭 (关闭后仍可读取缓冲区中剩余的数据) |
无缓冲 | 阻塞 (当发送方可用时,立即接收数据。) | 阻塞 (当接收方可用时,立即发送数据。) | 正常关闭 (关闭后接收方会立即收到零值和 false 。) |
已关闭 | 读取到零值和 false | panic | panic (再次关闭通道会引发 panic。) |
nil | 永久阻塞 | 永久阻塞 | panic |