Skip to content

通道

  • 通道(Channel)是用于 Goroutine 之间的数据传递。

定义

  • 格式:var ch chan int = make(chan int, length)
go
// 方式1
var ch1 chan int = make(chan int, 10)
// 方式2
ch2 := make(chan int, 10)

发送数据

  • 发送数据到通道:使用 <- 运算符将数据发送到通道。
go
// 定义一个通道
ch := make(chan int, 10)
// 发送数据到通道
ch <- 10
ch <- 20

接收数据

  • 从通道接收数据:使用 <- 运算符从通道接收数据。
go
// 定义一个通道
ch := make(chan int, 10)
// 发送数据到通道
ch <- 10
ch <- 20
// 从通道接收数据
x := <-ch

关闭通道

  • 关闭通道:使用 close() 函数关闭通道。
  • 关闭通道后,不能再发送数据到通道。
  • 关闭通道后,可以继续从通道接收数据,直到通道为空,通道取完了,之后灾区取出的结果0。
go
// 关闭通道
close(ch)

无缓冲管道(同步管道)

  • 无缓冲管道:在创建通道时不指定缓冲区大小,通道的发送和接收操作是同步的。
go
// 定义一个通道
ch := make(chan int)
// 发送数据到通道, 必须在另一协程操作, 否则会发生死锁
go func() {
    ch <- 10
    ch <- 20
}()
// 从通道接收数据
x := <-ch
y := <-ch
fmt.Println(x, y)

有缓冲管道(异步管道)

go
// 定义一个通道
ch := make(chan int,2)
// 可以同步操作,但是不能超过缓冲区大小
ch <- 10
ch <- 20
// 从通道接收数据
x := <-ch
y := <-ch
fmt.Println(x, y)

死锁分析

案例1

go
ch := make(chan int)
ch <- 10
  • 死锁原因:在无缓冲通道中,发送操作和接收操作是同步的,上述代码只有发送数据,没有接收数据,所以会发生死锁。
  • 解决方法:在发送数据之前,先启动一个协程接收数据。

案例2

go
ch := make(chan int)
ch <- 10
<-ch
  • 死锁原因:主 goroutine 执行 ch <- 10 时,由于是无缓冲通道,发送操作会阻塞,直到有其他 goroutine 执行对应的接收操作。但此时后续的 <-ch 代码根本没有机会执行(因为主 goroutine 已经在 ch <- 10 处阻塞了),导致没有接收方来解除阻塞,最终触发死锁。
  • 解决方法:在发送数据之前,先启动一个协程接收数据。
go
ch := make(chan int)
go func() {
    x := <-ch
    fmt.Println(x)
}()
ch <- 10

案例3

go
ch := make(chan int, 2)
ch <- 10
ch <- 20
ch <- 30
  • 死锁原因:缓存区满了,发送操作会死锁。
  • 解决方法:及时消耗管道数据,避免缓存区满。

单向管道

  • 管道可以被限制为只发送或只接收(通常用于函数参数,明确数据流向)
go
// 只发送管道:只能发送数据,不能接收
type SendChan chan<- int

// 只接收管道:只能接收数据,不能发送
type RecvChan <-chan int

// 函数参数为只发送管道
func sendData(ch chan<- int) {
	ch <- 100 // 允许发送
	// <-ch    // 错误:只发送管道不能接收
}

// 函数参数为只接收管道
func recvData(ch <-chan int) {
	fmt.Println(<-ch) // 允许接收
	// ch <- 200      // 错误:只接收管道不能发送
}

// 定义一个 只发送通道
ch := make(chan int)
go sendData(ch)
recvData(ch)

管道控制

  • select 语句可以同时监听多个通道,根据通道是否有数据可读或可写来执行不同的操作。
go
ch1 := make(chan int)

go func() {
    time.Sleep(2 * time.Second)
    ch1 <- 100
}()

ch2 := make(chan int, 2)

go func() {
    for i := 1; i < 10; i++ {
        ch2 <- i * 10
        time.Sleep(1 * time.Second)
    }
    close(ch2)
}()

for {
    // 循环监听通道,直到通道关闭
    select {
    case num1 := <-ch1:
        fmt.Println("收到数据ch1:", num1)
    case num2 := <-ch2:
        fmt.Println("收到数据ch2:", num2)
    default:
        fmt.Println("没有数据")
    }
}