目录
一、初识管道
channel
是 Go 语言中用于在 goroutine
之间进行通信和同步的一种机制。它可以安全地在不同的 goroutine
之间传递数据,并且可以用来实现同步操作。
channel
可以是无缓冲的(unbuffered)或有缓冲的(buffered)。
无缓冲的 channel
:发送操作会阻塞,直到另一个 goroutine
执行接收操作,反之亦然。
有缓冲的 channel
:可以存储一定数量的数据,发送操作会在缓冲区未满时立即完成,接收操作会在缓冲区不为空时立即完成。
二、无缓冲管道
package main
import (
"fmt"
"time"
)
func main() {
// 创建一个无缓冲的整数通道
ch := make(chan int)
// 启动一个匿名 goroutine,它将在一秒钟后向通道发送数据 10
go func() {
time.Sleep(time.Second)
ch <- 10
}()
// 从通道接收数据,此操作会阻塞,直到有数据发送到通道
num := <-ch
// 打印接收到的数据
fmt.Println(num)
}
代码解析:
ch := make(chan int)
:创建一个无缓冲的整数通道,用于在不同的goroutine
之间传递数据。go func() {...}
:启动一个匿名goroutine
,在其中使用time.Sleep(time.Second)
让该goroutine
暂停一秒钟,然后将整数10
发送到通道ch
中。num := <-ch
:从通道ch
中接收数据,由于通道是无缓冲的,此操作会阻塞,直到有数据发送到通道中。fmt.Println(num)
:将接收到的数据打印输出。
注意事项:
- 由于使用了
time.Sleep
来等待goroutine
发送数据,这不是一种可靠的同步方式,在实际应用中可以使用sync.WaitGroup
或其他同步机制来确保goroutine
完成任务。 - 无缓冲通道的发送和接收操作会相互阻塞,直到另一方准备好,这保证了数据的同步传递。
三、有缓冲管道
package main
import (
"fmt"
"time"
)
func main() {
// 创建一个有缓冲的通道,容量为 20
ch := make(chan int, 20)
// 向通道发送数据
ch <- 1
ch <- 2
ch <- 3
ch <- 4
// 启动一个 goroutine 向通道发送数据
go func() {
time.Sleep(1 * time.Second)
ch <- 3
}()
// 接收数据
fmt.Println(<-ch)
fmt.Println(<-ch)
fmt.Println(<-ch)
fmt.Println(<-ch)
fmt.Println(<-ch)
}
代码解析:
func main() {...}
:这是 Go 程序的主函数,程序从这里开始执行。ch := make(chan int, 20)
:创建一个有缓冲的通道ch
,其容量为 20,可存储 20 个整数。ch <- 1
,ch <- 2
,ch <- 3
,ch <- 4
:向通道ch
发送四个整数 1、2、3 和 4。由于通道是有缓冲的且缓冲区未满,这些发送操作不会阻塞。go func() {...}
:启动一个匿名 goroutine,在其中使用time.Sleep(1 * time.Second)
让该 goroutine 暂停一秒钟,然后将整数 3 发送到通道ch
中。fmt.Println(<-ch)
:从通道ch
中接收数据并打印。这里有五次接收操作,前四次接收的是之前发送的 1、2、3、4 中的数据,最后一次接收的是匿名 goroutine 发送的数据 3(如果它已经发送)。由于通道是有缓冲的,接收操作不会阻塞,除非缓冲区为空。
注意事项:
- 有缓冲通道在缓冲区未满时发送操作不会阻塞,在缓冲区不为空时接收操作不会阻塞。
四、close函数关闭管道
在 Go 语言中,close
函数用于关闭通道(channel)。关闭通道是一个重要的操作,它可以向接收方发送一个信号,表示不会再有数据发送到该通道。
示例:
package main
import (
"fmt"
)
func main() {
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
close(ch)
// 接收通道中的数据
for num := range ch {
fmt.Println(num)
}
}
代码解析:
ch := make(chan int, 3)
:创建一个有缓冲的通道,容量为 3。ch <- 1
,ch <- 2
,ch <- 3
:向通道发送数据。close(ch)
:关闭通道。for num := range ch {...}
:使用range
从通道中接收数据。当通道关闭时,range
循环会自动结束,不会阻塞。
注意事项:
- 关闭一个已经关闭的通道会导致 panic,所以在调用
close
函数之前,需要确保通道没有被关闭过。 - 向一个已经关闭的通道发送数据也会导致 panic。
- 可以使用
v, ok := <-ch
来检查通道是否关闭,其中ok
为false
表示通道已关闭。
检查通道是否关闭
package main
import (
"fmt"
)
func main() {
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
close(ch)
for {
val, ok := <-ch
if!ok {
fmt.Println("Channel is closed")
break
}
fmt.Println(val)
}
}
代码解析:
v, ok := <-ch
:从通道接收数据,ok
为true
表示接收成功,ok
为false
表示通道已关闭。if!ok {...}
:检查通道是否关闭,如果关闭则打印消息并退出循环。
使用 close
函数的场景:
- 当发送方完成数据发送后,可以关闭通道,通知接收方数据发送完毕。
- 在
select
语句中,关闭通道可以触发相应的case
分支。
五、通道循环取值
package main
import "fmt"
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
go func() {
for i := 0; i < 100; i++ {
ch1 <- i
}
close(ch1)
}()
go func() {
for {
val, ok := <-ch1
if !ok {
break
}
ch2 <- val * val
}
close(ch2)
}()
for i := range ch2 {
fmt.Println(i)
}
}
代码解析
func main() {...}
:这是 Go 程序的主函数,程序从这里开始执行。ch1 := make(chan int)
:创建一个无缓冲的整数通道ch1
,用于在不同的goroutine
之间传递数据。ch2 := make(chan int)
:创建另一个无缓冲的整数通道ch2
,用于在不同的goroutine
之间传递数据。go func() {...}
:启动第一个匿名goroutine
,在这个goroutine
中,使用for
循环将 0 到 99 的整数依次发送到ch1
通道中,发送完成后关闭ch1
通道。go func() {...}
:启动第二个匿名goroutine
,在这个goroutine
中,使用for
循环从ch1
通道接收数据。val, ok := <-ch1
:从ch1
通道接收数据,val
是接收到的数据,ok
表示通道是否关闭。if!ok {...}
:如果ok
为false
,表示ch1
通道已关闭,此时跳出循环。ch2 <- val * val
:将接收到的数据的平方发送到ch2
通道中。close(ch2)
:关闭ch2
通道。
for i := range ch2 {...}
:使用for
循环从ch2
通道接收数据,将接收到的数据打印输出。当ch2
通道关闭时,循环自动结束。
注意事项
- 这里使用
close
函数关闭通道,确保接收方知道数据发送完毕,避免死锁。 - 第一个
goroutine
发送数据,第二个goroutine
接收并处理数据,最后在主goroutine
中打印处理后的数据,体现了通道在不同goroutine
间的通信和数据处理流程。
六、单向通道
package main
import "fmt"
func connter(out chan<- int) {
for i := 0; i < 100; i++ {
out <- i
}
close(out)
}
func square(out chan<- int, in <-chan int) {
for i := range in {
out <- i * i
}
close(out)
}
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
go connter(ch1)
go square(ch2, ch1)
for i := range ch2 {
fmt.Println(i)
}
}
代码解析:
func connter(out chan<- int) {...}
:
- 这是一个名为
connter
的函数,接收一个只写通道out
(chan<- int
)作为参数。 for i := 0; i < 100; i++ {...}
:使用for
循环从 0 到 99 迭代。out <- i
:将迭代的数字i
发送到out
通道中。close(out)
:当循环结束后,关闭out
通道,向接收方发送信号表示数据发送完毕。
func square(out chan<- int, in <-chan int) {...}
:
- 这是一个名为
square
的函数,接收两个通道作为参数:out chan<- int
:只写通道,用于发送数据。in <-chan int
:只读通道,用于接收数据。
for i := range in {...}
:使用range
从in
通道接收数据,当in
通道关闭时,循环结束。out <- i * i
:将接收到的数据i
进行平方操作,并将结果发送到out
通道。close(out)
:当in
通道的数据全部接收并处理完后,关闭out
通道。
func main() {...}
:
ch1 := make(chan int)
:创建一个无缓冲的整数通道ch1
。ch2 := make(chan int)
:创建另一个无缓冲的整数通道ch2
。go connter(ch1)
:启动一个goroutine
并调用connter
函数,将ch1
作为参数传递给它,connter
函数会向ch1
发送 0 到 99 的数字。go square(ch2, ch1)
:启动另一个goroutine
并调用square
函数,将ch2
作为输出通道,ch1
作为输入通道,square
函数会从ch1
接收数据,将其平方后发送到ch2
。for i := range ch2 {...}
:使用range
从ch2
接收数据,并使用fmt.Println
打印接收到的数据,当ch2
关闭时,循环结束。
注意事项:
- 通道的使用确保了
goroutine
之间的安全通信和同步。 close
函数的使用表示数据发送完毕,避免接收方无限等待。- 代码中没有对可能的通道操作错误(如发送到已关闭的通道或从已关闭的通道接收)进行处理,在更复杂的应用中可能需要添加错误处理机制。
- 这里使用
range
从通道接收数据,当通道关闭时,range
循环会自动结束,这是一种简洁的处理方式。