channel详解

803 阅读3分钟

channel基础

channel是一种go协程用以接收或发送消息的安全的消息队列,channel就像两个go协程之间的先进先出的导管,来实现各种资源的同步.channel是用来在不同的 goroutine 中交换数据的,千万不要把 Channel 拿来在同一个 goroutine 中的不同函数之间间交换数据.

Channel 操作符<- 和操作方式

通信操作符 <- 的箭头指示数据流向,箭头指向哪里,数据就流向哪里,它是一个二元操作符,可以支持任意类型,对于 channel 的操作只有4种方式:

  • 创建 channel (通过make()函数实现,包括无缓存 channel 和有缓存 channel);

  • 向 channel 中添加数据(channel<-data);

  • 从 channel 中读取数据(data<-channel);

    • data<-channel, 从 channel 中接收数据并赋值给 data
    • <-channel,从 channel 中接收数据并丢弃
  • 关闭 channel(通过 close()函数实现)

    • 读取关闭后的无缓存通道,不管通道中是否有数据,返回值都为 0 和 false。
    • 读取关闭后的有缓存通道,将缓存数据读取完后,再读取返回值为 0 和 false。
    • 对于一个关闭的 channel,如果继续向 channel 发送数据,会引起 panic
    • channel 不能 close 两次,多次 close 会 panic

channel类型

无缓冲channel

无缓冲的 Channel 是最常见的形式,当你发送数据到 Channel 时,发送操作会阻塞,直到另一个 goroutine 从该 Channel 接收数据。

ch := make(chan int)

无缓冲的 Channel 会在发送和接收端都准备好之前阻塞

有缓冲channel

带缓冲的 Channel 实际上是一个阻塞队列。队列满时写协程会阻塞,队列空时读协程阻塞.

ch := make(chan int, 5)
  • make(chan int, 5) 创建了一个缓冲区大小为 5 的 Channel。

  • 可以向这个 Channel 发送最多 5 个数据而不会阻塞。如果缓冲区已满,发送操作会阻塞,直到有数据被接收。

只发送或只接收的 Channel

  • chan<- int 表示该 Channel 只能发送数据。

  • <-chan int 表示该 Channel 只能接收数据。

有时你可能需要限制 Channel 的方向性,使得它只能发送或接收数据。这可以通过在函数参数中指定 Channel 的方向来实现.方向性 Channel 在 Go 语言中主要用于提高代码的安全性和可读性,通过限制 Channel 的数据流向来防止误用。

// 生产者函数,只发送数据
func producer(ch chan<- int) {
    for i := 0; i < 5; i++ {
        fmt.Printf("Producing %d\n", i)
        ch <- i
        time.Sleep(time.Second)
    }
    close(ch) // 关闭 Channel,表示不再发送数据
}

// 消费者函数,只接收数据
func consumer(ch <-chan int) {
    for value := range ch {
        fmt.Printf("Consuming %d\n", value)
    }
}

func main() {
    ch := make(chan int)

    go producer(ch) // 启动生产者 goroutine
    consumer(ch)    // 启动消费者函数(可以在同一个 goroutine 中执行)
}

channel的遍历

for range

channel支持 for range 的方式进行遍历:

func main() {  
    ci := make(chan int5)  
    for i := 1; i <= 5; i++ {
        ci <- i
    }    
    close(ci)  

    for i := range ci {  
        fmt.Println(i)  
    }  
}  

值得注意的是,在遍历时,如果channel 没有关闭,那么会一直等待下去;如果在遍历时channel已经关闭,那么在遍历完数据后自动退出遍历。也就是说,for range 的遍历方式时阻塞型的遍历方式。

for select

select可以处理非阻塞式消息发送、接收及多路选择。

func main() {  
  
    c1 := make(chan string)  
    c2 := make(chan string)  
  
    go func() {  
        time.Sleep(1 * time.Second)  
        c1 <- "one"  
    }()  
    go func() {  
        time.Sleep(2 * time.Second)  
        c2 <- "two"  
    }()  
  
    for i := 0; i < 2; i++ {  
        select {  
        case msg1 := <-c1:  
            fmt.Println("received", msg1)  
        case msg2 := <-c2:  
            fmt.Println("received", msg2)  
        }  
    }  
}

//received one
//received two

select中有case代码块,用于channel发送或接收消息,任意一个case代码块准备好时,执行其对应内容;多个case代码块准备好时,随机选择一个case代码块并执行;所有case代码块都没有准备好,则等待;还可以有一个default代码块,所有case代码块都没有准备好时执行default代码块。

引用文章